Cet article est basé sur ❷ de Collecte et classification des informations relatives à l'apprentissage automatique (concept).
Pour classer automatiquement les pages Web collectées par exploration, comme indiqué précédemment dans Classification multi-étiquettes par forêt aléatoire avec scikit-learn, forêt aléatoire et Il est possible de classer dans un environnement sur site avec un algorithme tel qu'un arbre de décision, mais d'un autre côté, comme exemple d'utilisation du Watson Natural Language Classifier de Ruby Il est également possible d'utiliser des services sur le cloud.
Le prétraitement tel que décrit dans Types de prétraitement dans le traitement du langage naturel et sa puissance est très important.
Cependant, si vous souhaitez utiliser un service sur le cloud pour classer automatiquement les pages Web, vous devez effectuer un prétraitement non mentionné dans cet article.
C'est le "Résumé de la page Web".
En règle générale, les services sur le cloud ont une limite sur la quantité de données par enregistrement. Exemple d'utilisation de Watson Natural Language Classifier de Ruby a une limitation de 1024 caractères par article à classer, donc découpez jusqu'à 1024 octets à partir du début de l'article de blog J'ai essayé de le transmettre au service. C'est aussi un «résumé» rudimentaire.
Dans le cas des pages Web, il n'y a pas de limite sur la quantité de données, il est donc nécessaire de concevoir des moyens de réduire la quantité de données transmises aux services sur le cloud sans perdre les informations requises pour la classification.
Je pense que la technique la plus orthodoxe pour cela est la "synthèse de pages Web".
Je n'ai pas trouvé beaucoup de tendances récentes en matière de technologie de résumé, mais il y a environ trois ans, Tendances de la recherche sur la technologie de résumé automatique A été utile.
Aussi, en utilisant LexRank comme implémentation concrète du code je vais résumer le discours de Donald Trump en trois lignes avec l'algorithme de synthèse automatique LexRank ) Et Résumé de la valeur du produit du site EC utilisant l'algorithme de synthèse automatique LexRank sont publiés sur Qiita.
Ces implémentations résument les phrases avec la politique de traiter les phrases comme un ensemble de «phrases», d'attribuer de l'importance aux «phrases» et d'extraire dans l'ordre des «phrases» les plus importantes.
La question ici est de savoir comment diviser une page Web en «phrases».
À ce propos, je n'ai pas pu trouver le code d'implémentation même si je cherchais sur le net, et la seule logique qui pouvait être implémentée était ["Division de texte HTML pour la synthèse de pages Web en extrayant des phrases importantes"](http: / /harp.lib.hiroshima-u.ac.jp/hiroshima-cu/metadata/5532) était le papier [^ 1].
Après tout, je n'ai pas trouvé le code d'implémentation, donc cette fois j'ai décidé d'implémenter le code [^ 2] avec la logique de cet article.
La logique spécifique comprend les trois étapes suivantes.
(1) Divisez le tout en unités de texte (phrases candidates) La pause est en dessous
(2) Classer les unités de texte en unités de phrase et unités sans phrase La peine doit remplir au moins trois des conditions suivantes --C1. Le nombre de mots indépendants est de 7 ou plus --C2. Le rapport entre le nombre de mots indépendants et le nombre total de mots est égal ou inférieur à 0,64 --C3. Le rapport entre le nombre de mots attachés et le nombre de mots indépendants est égal ou supérieur à 0,22 [^ 3] --C4 Le rapport entre le nombre de mots auxiliaires et le nombre de mots indépendants est de 0,26 ou plus. --C5 Le rapport entre le nombre de verbes auxiliaires et le nombre de mots indépendants est de 0,06 ou plus.
(3) Combinez et divisez les unités sans phrase en fonction du nombre de mots indépendants
Par souci de simplicité cette fois, l'unité sans instruction se joint uniquement et n'implémente pas le fractionnement.
De plus, le code ci-dessous est implémenté pour extraire uniquement la partie corps du HTML, car il est supposé que le contenu de l'élément title est utilisé pour le nom de fichier du fichier HTML téléchargé. En général, c'est une bonne idée d'utiliser également le contenu de l'élément title dans le résumé.
html2plaintext_part_1.py
import codecs
import re
class Article:
#Essayez les codes de caractères dans cet ordre
encodings = [
"utf-8",
"cp932",
"euc-jp",
"iso-2022-jp",
"latin_1"
]
#Expression normale d'extraction d'élément de niveau bloc
block_level_tags = re.compile("(?i)</?(" + "|".join([
"address", "blockquote", "center", "dir", "div", "dl",
"fieldset", "form", "h[1-6]", "hr", "isindex", "menu",
"noframes", "noscript", "ol", "pre", "p", "table", "ul",
"dd", "dt", "frameset", "li", "tbody", "td", "tfoot",
"th", "thead", "tr"
]) + ")(>|[^a-z].*?>)")
def __init__(self,path):
print(path)
self.path = path
self.contents = self.get_contents()
def get_contents(self):
for encoding in self.encodings:
try:
lines = ' '.join([line.rstrip('\r\n') for line in codecs.open(self.path, 'r', encoding)])
parts = re.split("(?i)<(?:body|frame).*?>", lines, 1)
if len(parts) == 2:
head, body = parts
else:
print('Cannot split ' + self.path)
body = lines
body = re.sub(r"(?i)<(script|style|select).*?>.*?</\1\s*>"," ", body)
body = re.sub(self.block_level_tags, ' _BLOCK_LEVEL_TAG_ ', body)
body = re.sub(r"(?i)<a\s.+?>",' _ANCHOR_LEFT_TAG_ ', body)
body = re.sub("(?i)</a>",' _ANCHOR_RIGHT_TAG_ ', body)
body = re.sub("(?i)<[/a-z].*?>", " ", body)
blocks = []
for block in body.split("_BLOCK_LEVEL_TAG_"):
units = []
for unit in block.split("。"):
unit = re.sub("_ANCHOR_LEFT_TAG_ +_ANCHOR_RIGHT_TAG_", " ", unit) #Exclure les liens vers des images
if not re.match(r"^ *$", unit):
for fragment in re.split("((?:_ANCHOR_LEFT_TAG_ .+?_ANCHOR_LEFT_TAG_ ){2,})", unit):
fragment = re.sub("_ANCHOR_(LEFT|RIGHT)_TAG_", ' ', fragment)
if not re.match(r"^ *$", fragment):
if TextUnit(fragment).is_sentence():
#Les unités de relevé se terminent par "."
if len(units) > 0 and units[-1] == '―':
units.append('。\n')
units.append(fragment)
units.append(' 。\n')
else:
#Les unités sans phrase se terminent par "-".
# (Contrainte)Contrairement à l'article, les unités sans phrase sont uniquement combinées et non divisées.
units.append(fragment)
units.append('―')
if len(units) > 0 and units[-1] == '―':
units.append('。\n')
blocks += units
return re.sub(" +", " ", "".join(blocks))
except UnicodeDecodeError:
continue
print('Cannot detect encoding of ' + self.path)
return None
html2plaintext_part_2.py
from janome.tokenizer import Tokenizer
from collections import defaultdict
import mojimoji
#import re
class TextUnit:
tokenizer = Tokenizer("user_dic.csv", udic_type="simpledic", udic_enc="utf8")
def __init__(self,fragment):
self.fragment = fragment
self.categories = defaultdict(int)
for token in self.tokenizer.tokenize(self.preprocess(self.fragment)):
self.categories[self.categorize(token.part_of_speech)] += 1
def categorize(self,part_of_speech):
if re.match("^nom,(Général|代nom|固有nom|Changer de connexion|[^,]+tige)", part_of_speech):
return 'Indépendance'
if re.match("^verbe", part_of_speech) and not re.match("Sa étrange", part_of_speech):
return 'Indépendance'
if re.match("^adjectif,Indépendance", part_of_speech):
return 'Indépendance'
if re.match("^Particule", part_of_speech):
return 'Particule'
if re.match("^Verbe auxiliaire", part_of_speech):
return 'Verbe auxiliaire'
return 'Autre'
def is_sentence(self):
if self.categories['Indépendance'] == 0:
return False
match = 0
if self.categories['Indépendance'] >= 7:
match += 1
if 100 * self.categories['Indépendance'] / sum(self.categories.values()) <= 64:
match += 1
if 100 * (self.categories['Particule'] + self.categories['Verbe auxiliaire']) / self.categories['Indépendance'] >= 22:
#Interprété comme "mot attaché = verbe auxiliaire ⋃ verbe auxiliaire" selon l'article(Différent de la définition habituelle)
match += 1
if 100 * self.categories['Particule'] / self.categories['Indépendance'] >= 26:
match += 1
if 100 * self.categories['Verbe auxiliaire'] / self.categories['Indépendance'] >= 6:
match += 1
return match >= 3
def preprocess(self, text):
text = re.sub("&[^;]+;", " ", text)
text = mojimoji.han_to_zen(text, digit=False)
text = re.sub('(\t | )+', " ", text)
return text
html2plaintext_part_3.py
if __name__ == '__main__':
import glob
import os
path_pattern = ''/home/samba/example/links/bookmarks.crawled/**/*.html'
# The converted plaintext is put as '/home/samba/example/links/bookmarks.plaintext/**/*.txt'
for path in glob.glob(path_pattern, recursive=True):
article = Article(path)
plaintext_path = re.sub("(?i)html?$", "txt", path.replace('.crawled', '.plaintext'))
plaintext_dir = re.sub("/[^/]+$", "", plaintext_path)
if not os.path.exists(plaintext_dir):
os.makedirs(plaintext_dir)
with open(plaintext_path, 'w') as f:
f.write(article.contents)
Je ne suis pas familier avec Python, donc je pense que c'est un code maladroit, mais je vous serais reconnaissant si vous pouviez signaler des améliorations.
Le texte brut généré de cette manière doit être résumé en utilisant quelque chose comme LexRank.
Dans cet exemple, janome a été utilisé pour l'analyse morphologique par souci de simplicité, mais comme "HTML-> texte brut" et "texte brut-> résumé" ont des étapes séparées, "texte brut-> résumé" est complètement différent. Vous pouvez également utiliser l'outil d'analyse morphologique de.
[^ 1]: La recherche originale était de 2002 à 2004, et une logique plus efficace peut avoir été proposée récemment.
Recommended Posts