本章导学
- 学习目标:
- 掌握如何进行fasttext模型的特征工程以及训练过程, 并实现它们.
- 引导分析:
- 进行几乎是标准化模型训练流程.
- fasttext模型,站在多模型训练与预测的效率角度上,使结构设计非常简单,以至于可能无法有效捕捉上下文的语义关系,因此加入n-gram特征进行了补充.
- 进行fasttext模型的特征工程以及训练过程的七步诗:

-
本章小节:
- 3.1 获取训练语料
- 学习如何从原始语料生成训练语料.
- 3.2 进行数据分析
- 学习使用训练语料进行一些统计分析工作.
- 3.3 特征处理
- 学习将n-gram作为特征加入到初始特征中.
- 3.4 构建模型结构
- 学习fasttext的模型的组成及其各个层的作用.
- 3.5 选取损失函数和优化方法
- 学习模型选择的损失函数和优化方法的数学原理和优缺点.
- 3.6 进行模型训练
- 学习模型在训练语料上的训练过程.
- 3.7 模型的保存与加载
- 学习训练之后的模型如何进行保存与加载.
- 3.1 获取训练语料
3.1 获取训练语料
- 学习目标:
- 掌握实现获取训练语料过程的三步曲.
- 获取训练语料过程的三步曲:
- 第一步: 明确原始数据来源.
- 第二步: 定义正负样本.
- 第三步: 提取正负样本语料.
- 明确原始数据来源:
- 公司内容生产小组提供的各种类型的文章.
- 定义正负样本:
- 将文章中的每一条句子作为该类别的正样本; 将其他类别文章中的每一条句子作为负样本.
- 提取正负样本:
- 先提取正样本语料,再在正样本基础上,提取正负样本语料.
- 提取正负样本过程的代码分析:
import os
# 限制句子的最小字符数和句子的最大字符数
MIN_LENGTH = 5
MAX_LENGTH = 500
def get_p_text_list(single_article_path):
"""获取单篇文章的文本列表"""
with open(single_article_path, "r") as f:
text = f.read()
# 去掉换行符, 并以句号划分
cl = text.replace("\n", ".").split("。")
# 过滤掉长度范围之外的句子
cl = list(filter(lambda x: MIN_LENGTH<len(x)<MAX_LENGTH, cl))
return cl
def get_p_sample(a_path, p_path):
"""该函数用于获得正样本的csv, 以文章路径和正样本csv写入路径为参数"""
if not os.path.exists(a_path): return
if not os.path.exists(p_path): os.mkdir(p_path)
# 以追加的方式打开预写入正样本的csv
fp = open(os.path.join(p_path, "p_sample.csv"), "a")
# 遍历文章目录下的每一篇文章
for u in os.listdir(a_path):
cl = get_p_text_list(os.path.join(a_path, u))
for clc in cl:
fp.write("1" + "\t" + clc + "\n")
fp.close()
def get_sample(p_path, n_path_csv_list: list):
"""该函数用于获取样本集包括正负样本, 以正样本csv文件路径和负样本csv文件路径列表为参数"""
fp = open(os.path.join(p_path, "sample.csv"), "w")
with open(os.path.join(p_path, "p_sample.csv"), "r") as f:
text = f.read()
# 先将正样本写入样本csv之中
fp.write(text)
# 遍历负样本的csv列表
for n_p_c in n_path_csv_list:
with open(n_p_c, "r") as f:
# 将其中的标签1改写为0
text = f.read().replace("1", "0")
# 然后写入样本的csv之中
fp.write(text)
fp.close()
- 代码位置: 代码将写在/data/django-uwsgi/text_labeled/model_train/get_sample.py中。
- 函数: get_p_text_list(single_article_path)
- 输入实例:
# 原始语料在上一个目录的create_graph目录下
single_article_path = "../create_graph/beauty/article-191721"
- 输出效果:
['卡姿兰携手全新品牌代言人威神V三子黄旭熙、钱锟、刘扬扬,引爆丝绒红唇诱惑魅力2019年7月23日,时尚彩妆领导品牌卡姿兰(Carslan)正式官宣,来自新晋人气男团威神V(WayV)的三位成员黄旭熙LUCAS、钱锟KUN、刘扬扬YANGYANG成为全新品牌代言人',
'三位兼具帅气外表的实力派偶像自出道以来,便以各自独特的音乐个性和气质魅力征服了一众粉丝',
'同为年轻实力派,此次卡姿兰携手三人,以丝绒唇膏的极致魅力引爆时尚热点',
'卡姿兰正式官宣全新品牌代言人钱锟KUN、黄旭熙LUCAS、刘扬扬YANGYANG 无畏年轻实力派,极致演绎丝绒魅力出道不满一年却备受关注的中国男团威神V,以独特的音乐才能和超强的艺能感为中国乐坛注入了新鲜血液',
'成员黄旭熙LUCAS、钱锟KUN、刘扬扬YANGYANG更以不俗的舞蹈实力与极具特色的音乐演绎技巧,在舞台上彰显出极强的个人气场',
'气质美少年黄旭熙LUCAS凭借着精致五官和完美身材,成为备受粉丝追捧的“撕漫男”;威神V队长钱锟KUN有着温柔声线,尽显阳光暖男的成熟气质;年纪最小的刘扬扬YANGYANG可谓组合当中的鬼马精灵,身为德国华裔的他极富语言天赋', ' 在全新广告片中,三位男神牵手卡姿兰气场全开,演绎出高级丝绒时代的诱惑魅力',
'在此系列中,黄旭熙LUCAS代言的卡姿兰丝绒10号色斗士唇膏,以复古深红显色而不挑肤的百搭特点,轻松驾驭各大场面,为新时代女性气场霸气加持']
- 函数: get_p_sample(a_path, p_path)
- 输入实例:
# 原始语料在上一个目录的create_graph目录下
a_path = "../create_graph/beauty/"
p_path = "./beauty"
- 输出效果:
# 获得一个p_sample.csv文件, 里面的内容形如:
1 这一次的《流星花园》主题体验馆如梦如幻,激荡起无数少女心,那么植物医生下一次制造的惊喜我们翘首以盼
1 美妆福利时间又到啦!本期免费试用产品:菲洛嘉新肌赋活乳霜价格/规格:739RMB/50ml试用总量:10份截至申请日期:2018年12月4日试用报告截至日期:2018年1月4日菲洛嘉新肌赋活乳霜,即新肌赋活面霜哑光型,更适合中性及混合肤质
1 高含量*NCTF是抵御肌肤老化、多重赋活美肌的理想解决方案
1 -盈润抚纹:透明质酸结合多肽,促进透明质酸生成,平滑纹路,密集丰盈肌肤;-紧致饱满:胶原蛋白促进因子**加强肌肤纤维支撑,结合透明质酸紧实弹韧肌肤;-焕亮气色:维他命C舒缓肌肤均匀肤色;维他命复合物A+H+E结合多酚植物精华,平滑肌理,焕发光泽,强化肌肤屏障,提升抵御力;-抑制油光:橄榄果酸+植物精华减少油脂分泌、收缩毛孔,持久营造哑光无油肤质
-
练一练:
- 请同学们按照此方法, 将我们在目录/data/django-uwsgi/text_labeled/create_graph下, 给出的fashion, movie, star三个原始语料文件, 提取出它们正样本. 最终在对应的目录下生成p_sample.csv文件.
- 函数: get_sample(p_path, n_path_csv_list)
- 输入实例:
p_path = "./beauty"
n_path_csv_list = ["./movie/p_sample.csv", "./star/p_sample.csv", "./fashion/p_sample.csv"]
- 输出效果:
# 查看前5条的正样本:
1 #PINKGANG#:不粉所有人纪梵希2018全新禁忌之吻漆光唇蜜发布会暨派对 2018年8月19日,法国品牌纪梵希特别打造了一场精致酷炫的#PINKGANG#时尚派对,为庆祝纪梵希全新禁忌之吻漆光唇蜜(Gloss Interdit Vinyl)的闪耀上市
1 上海黄浦江畔的德必外滩8号,在历史悠久的法式古典建筑中,纪梵希与国内各大时尚媒体、时尚美妆领域的达人以及时髦人士一起分享并体验品牌的全新产品,感受品牌时尚叛逆的奢华魅力
1 超人气青春偶像陈立农作为品牌挚友出席活动,演员胡冰卿、陈瑶、李兰迪代表新时代潮流标杆受邀一同亮相派对,共同分享纪梵希全新唇蜜的炫目发布
1 值此之际,围绕着“#PINKGANG#不粉所有人”的主题,纪梵希为来宾营造出叛逆时髦、不受约束的派对氛围,充分展示了品牌一贯以来突破经典、个性前卫的态度
1 现场的布置以禁忌之吻漆光唇蜜的色彩为灵感,将霓虹粉色作为场馆的主色调,突显纪梵希全新产品神秘、禁忌、时尚的风格
# 查看后5条的负样本:
0 它采用皮革饰边塑造柔软休闲款式的轮廓,激光切割顶部
0 绉胶鞋底非常耐穿
0 系带的款式也很时髦哦~皮毛是天然连毛小绵羊皮,来自瑞士
0 皮革采用小绵羊皮,材质非常好,所以特别保暖,价格自然也会贵一些
0 大家可去Shopbop上购买,价格比UGG贵很多,但是质量也好~品牌:INUIKI 官网链接戳这←单品购买链接戳这←最后一句哎哟喂,这双00刚结束,又要开始剁手买雪地靴了!
-
练一练:
- 请同学们按照此方法, 将我们在目录/data/django-uwsgi/text_labeled/create_graph下, 给出的fashion, movie, star三个原始语料文件, 提取出它们正负样本, 最终在对应的目录下生成sample.csv文件.
- 主要注释:
# 限制句子的最小字符数和句子的最大字符数
"""获取单篇文章的文本列表"""
# 去掉换行符, 并以句号划分
# 过滤掉长度范围之外的句子
"""该函数用于获得正样本的csv, 以文章路径和正样本csv写入路径为参数"""
# 以追加的方式打开预写入正样本的csv
# 遍历文章目录下的每一篇文章
"""该函数用于获取样本集包括正负样本, 以正样本csv文件路径和负样本csv文件路径列表为参数"""
# 先将正样本写入样本csv之中
# 遍历负样本的csv列表
# 将其中的标签1改写为0
# 然后写入样本的csv之中
-
小节总结:
- 学习了获取训练语料过程的三步曲:
- 第一步: 明确原始数据来源.
- 第二步: 定义正负样本.
- 第三步: 提取正负样本语料.
- 学习了明确原始数据来源:
- 公司内容生产小组提供的各种类型的文章.
- 学习了定义正负样本:
- 将文章中的每一条句子作为该类别的正样本; 将其他类别文章中的每一条句子作为负样本.
- 学习并实现了提取正负样本:
- 先提取正样本语料,再在正样本基础上,提取正负样本语料.
- 学习了获取训练语料过程的三步曲:
3.2 进行数据分析
- 学习目标:
- 掌握文本数据必要的分析过程并理解它们的作用.
- 文本数据必要的分析过程:
- 获取正负样本的分词列表和对应的标签.
- 获取正负标签数量分布.
- 获取句子长度分布.
- 获取常见词频分布.
- 获取正负样本的分词列表和对应的标签
- 作用: 为进行可视化数据分析, 如获取正负标签数量分布, 获取句子长度分布, 获取常见词频分布等作数据准备.
- 获取正负标签数量分布
- 作用: 用于帮助调整正负样本比例, 而调整正负样本比例, 对我们进行接下来的数据分析和判断模型准确率基线起到关键作用.
- 举个栗子:
假如正样本数量为8万条, 负样本数量为2万条,
如果我们不做任何样本调整工作, 就将其输送给模型训练,
那么模型很快就能找到一个规律, 就是不分青红皂白的都把结果预测为正样本,
也可以得到差不多80%的准确率.
这就说明我们此时模型的准确率基线就是80%, 因此这是不合理的,
所以我们必须均衡正负样本, 保证模型基线在50%左右, 这样得到准确率80%的模型才算得上有意义.
- 获取句子长度分布
- 作用: 用于帮助判断句子合理的截断对齐长度, 而合理的截断长度将有效的避免稀疏特征或冗余特征的产生, 提升训练效率.
- 举个栗子:
假如我们有10万条数据, 其中9.9万条的句子长度都在100个字符内,
只有剩下的一小部分句子长度非常长,
因为模型的输入需要是矩阵, 也就是等长向量, 因此文本需要进行截断对齐,
而分析在什么长度进行截断对齐十分重要,
如果我们以最长长度对齐的话, 将有9.9万条数据扩充没有必要的特征长度,
不仅占用更多的内存, 并且导致模型参数激增, 模型训练效率下降.
- 获取常见词频分布
- 作用: 指导之后模型超参数max_feature(最大的特征总数)的选择和初步评估数据质量.
- 解释:
模型超参数max_feature的选定在NLP领域一般是大于所有语料涉及的词汇总数的,
但是又不能选择过于大, 否则会导致网络参数激增, 模型过于复杂,容易过拟合.
因此需要参考词汇总数.
同时, 词频分布可以看出高频词汇与类别的相关性, 判断正负样本的是否对分类产生效果.
- 获取正负样本分词列表和对应的标签过程的代码分析:
import pandas as pd
import jieba
def get_data_labels(csv_path):
"""获得训练数据和对应的标签, 以正负样本的csv文件路径为参数"""
# 使用pandas读取csv文件至内存
df = pd.read_csv(csv_path, header=None, sep="\t")
# 对句子进行分词处理并过滤掉长度为1的词
train_data = list(map(lambda x: list(filter(lambda x: len(x)>1,
jieba.lcut(x))), df[1].values))
# 取第0列的值作为训练标签
train_labels = df[0].values
return train_data, train_labels
- 代码位置: 代码将写在/data/django-uwsgi/text_labeled/model_train/data_analysis.py中.
- 输入实例:
# 样本csv文件路径
csv_path = "./movie/sample.csv"
- 输出效果:
# train_data
[['今日', '捉妖', '北京', '举办', '全球', '首映礼', '发布会', '导演'],
['许诚毅', '各位', '主演', '亮相', '现场', '各位', '演员'],
[ '大家', '讲述', '自己', '拍摄', '过程', '发生', '趣事', '大家', '分享'],
[ '自己', '过年', '计划', '现场', '布置', '十分', '喜庆', '放眼望去', '一片', '红彤彤', '颜色', '感受', '浓浓的', '年味'],
['胡巴', '遭遇', '危险', '全民', '追击', '发布', '预告片', '当中', '胡巴'],
['遭到', '追杀', '流落', '人间', '梁朝伟', '收留', '与此同时'], ...]
# train_labels
[1 1 1 ... 0 0 0]
- 获取标签类别数量分布过程的代码分析:
import os
from collections import Counter
def pic_show(pic, pic_path, pic_name):
"""用于图片显示,以图片对象和预保存的路径为参数"""
if not os.path.exists(pic_path): os.mkdir(pic_path)
pic.savefig(os.path.join(pic_path, pic_name)
print("请通过地址http://47.92.175.143:8087/text_labeled/model_train" + pic_path[1:] + pic_name + "查看.")
def get_labels_distribution(train_labels, pic_path, pic_name="ld.png"):
"""获取正负样本数量的基本分布情况"""
# class_dict >>> {1: 3995, 0: 4418}
class_dict = dict(Counter(train_labels))
print(class_dict)
df = pd.DataFrame(list(class_dict.values()), list(class_dict.keys()))
pic = df.plot(kind='bar', title="类别分布图").get_figure()
pic_show(pic, pic_path, pic_name)
- 代码位置: 代码将写在/data/django-uwsgi/text_labeled/model_train/data_analysis.py中.
- 输入实例:
# get_data_labels函数得到的train_labels
# 图片的存储路径
pic_path = "./movie/"
# 图片的名字, 默认是ld.png
pic_name = "ld.png"
- 输出效果:
{1: 4640, 0: 7165}
标签类别数量分布图, 位于./movie/ld.png

- 结果分析:
- 当前的正负样本数量是分别是: 4640和7165,相差2525条数据.
- 为了使正负样本均衡, 让它们的比例为1:1, 我们将在之后进行的该类别的数据分析和模型训练中, 去掉2525条负样本的数量.
- 获取句子长度分布过程的代码分析:
def get_sentence_length_distribution(train_data, pic_path, pic_name="sld.png"):
"""该函数用于获得句子长度分布情况"""
sentence_len_list = list(map(len, train_data))
# len_dict >>> {38: 62, 58: 18, 40: 64, 35: 83,....}
len_dict = dict(Counter(sentence_len_list))
len_list = list(zip(len_dict.keys(), len_dict.values()))
# len_list >>> [(1, 3), (2, 20), (3, 51), (4, 96), (5, 121), (6, 173), ...]
len_list.sort(key=(lambda x: x[0]))
df = pd.DataFrame(list(map(lambda x: x[1], len_list)), list(
map(lambda x: x[0], len_list)))
ax = df.plot(kind='bar', figsize=(18, 18), title="句子长度分布图")
ax.set_xlabel("句子长度")
ax.set_ylabel("该长度出现的次数")
pic = ax.get_figure()
pic_show(pic, pic_path, pic_name)
- 代码位置: 代码将写在/data/django-uwsgi/text_labeled/model_train/data_analysis.py中.
- 输入实例:
# 通过get_data_labels得到的train_data(需要进行均衡切片)
# 图片的存储路径
pic_path = "./movie/"
# 图片的名字, 默认是sld.png
pic_name = "sld.png"
- 输出效果:
句子长度分布图, 位于./movie/sld.png

- 结果分析:
- 通过句子长度分布图, 我们知道了句子的长度范围在0-151之间.
- 但在0-60的长度之间, 已经包含了超过90%的句子, 因此这里可以认为60的长度是一个合理的截断对齐长度, 即不会使大量句子被截断而失去主要信息, 又能够有效避免补齐的特征数量太多, 导致模型参数过大.
- 获取常见词频分布的代码分析过程:
from itertools import chain
def get_word_frequency_distribution(train_data, pic_path, pic_name="wfd.png"):
"""该函数用于获得词频分布"""
vocab_size = len(set(chain(*train_data)))
print("所有样本共包含不同词汇数量为:", vocab_size)
# 获取常见词分布字典,以便进行绘图
# common_word_dict >>> {'电影': 1548, '自己': 968, '一个': 850, '导演': 757, '现场': 744, ...}
common_word_dict = dict(Counter(chain(*train_data)).most_common(50))
df = pd.DataFrame(list(common_word_dict.values()),
list(common_word_dict.keys()))
pic = df.plot(kind='bar', figsize=(18, 18), title="常见词分布图").get_figure()
pic_show(pic, pic_path, pic_name)
- 代码位置: 代码将写在/data/django-uwsgi/text_labeled/model_train/data_analysis.py中.
- 输入实例:
# 通过get_data_labels得到的train_data(需要进行均衡切片)
# 图片的存储路径
pic_path = "./movie/"
# 图片的名字, 默认是wfd.png
pic_name = "wfd.png"
- 输出效果:
所有样本共包含不同词汇数量为:24020
电影训练集常见词分布图, 位于./movie/wfd.png

- 结果分析:
- 通过常见词频分析, 全文词汇总数为24020, 在模型训练时定义的max_features应大于该数值.
- 同时对比高频词汇中出现的与影视相关的词汇占比大概在50%左右, 符合正负样本的分布比例, 因此语料质量尚可.
- 主要注释:
"""获得训练数据和对应的标签, 以正负样本的csv文件路径为参数"""
# 使用pandas读取csv文件至内存
# 对句子进行分词处理并过滤掉长度为1的词
# 取第0列的值作为训练标签
"""用于图片显示,以图片对象和预保存的路径为参数"""
"""获取正负样本数量的基本分布情况"""
# class_dict >>> {1: 3995, 0: 4418}
"""该函数用于获得句子长度分布情况"""
# len_dict >>> {38: 62, 58: 18, 40: 64, 35: 83,....}
# len_list >>> [(1, 3), (2, 20), (3, 51), (4, 96), (5, 121), (6, 173), ...]
"""该函数用于获得词频分布"""
# 获取常见词分布字典,以便进行绘图
# common_word_dict >>> {'电影': 1548, '自己': 968, '一个': 850, '导演': 757, '现场': 744, ...}
- 练一练:
- 请同学们按照上面学习的数据分析方法, 对其他给出的训练数据(fashion, beauty, star), 进行类似的数据分析, 并在对应的文件中生成三张分布图.
-
小节总结:
- 学习了文本数据分析的过程:
- 获取正负样本的分词列表和对应的标签.
- 获取正负标签数量分布.
- 获取句子长度分布.
- 获取常见词频分布.
- 学习并实现了获取正负样本的分词列表和对应的标签.
- 作用: 为进行可视化数据分析作数据准备.
- 实现函数: get_data_labels
- 学习并实现了获取正负标签数量分布.
- 作用: 用于帮助调整正负样本比例, 而调整正负样本比例, 对我们进行接下来的数据分析和判断模型准确率基线起到关键作用.
- 实现函数: get_labels_distribution
- 学习并实现了获取句子长度分布.
- 作用: 用于帮助判断句子合理的截断长度.
- 实现函数: get_sentence_length_distribution
- 学习并实现了获取常见词频分布.
- 作用: 指导之后模型超参数max_features的选择和初步评估数据质量.
- 实现函数: get_word_frequency_distribution
- 学习了文本数据分析的过程:
3.3 特征处理
- 学习目标:
- 掌握实现特征处理过程的四步曲及其作用.
- 特征处理过程的四步曲:
- 第一步: 进行词汇映射
- 第二步: 将向量进行合适截断
- 第三步: 加入n-gram特征
- 第四步: 将向量进行最长补齐
- 进行词汇映射:
- 作用: 将分词列表中的每个词映射成数字.
- 举个栗子
分词(词汇)列表: ["有时", "我想", "放弃", "挣扎", "也放下", "我", "写字的手"]
把每个词映射成数字, 得到序列列表: [1, 2, 3, 4, 5, 6, 7]
分词列表: ["可是", "我已经", "放弃", "太多", "还坚持", "着说", "还坚持", "着走"]
得到序列列表: [8, 9, 3, 10, 11, 12, 11, 13]
- 将向量进行合适截断对齐:
- 作用: 将映射后的句子向量进行截断,以降低模型输入的特征维度,来防止过拟合.
- 举个栗子:
序列列表: [[1, 2, 3, 4, 5, 6], [1, 3, 9], [2, 4, 6], [2, 3]]
以长度3进行截断对齐后得到
新的序列列表: [[1, 2, 3], [1, 3, 9], [2, 4, 6], [2, 3, 0]]
- 加入n-gram特征:
- 作用: 将n-gram表示作为特征,能够补充特征中没有上下文关联的缺点,将有效帮助模型捕捉上下文的语义关联.
- 举个栗子:
在这里, 可以将n-gram特征可以理解为相邻词汇的共现特征, 当n为2时, 就是连续两个词的共现.
我们这里将使用2-gram, 因此以2-gram为例进行解释:
分词列表: ["是谁", "敲动", "我心"]
对应的序列列表: [1, 34, 21]
我们可以认为序列列表中的每个数字就是原始句子的特征, 即词汇是原始句子的特征.
除此之外, 我们还可以把"是谁"和"敲动"两个词共同出现且相邻也作为一种特征加入到序列列表中,
此时序列列表就变成了包含2-gram特征的特征列表: [1, 34, 21, 1000]
这里的1000就代表"是谁"和"敲动"共同出现且相邻, 这种特征也就是n-gram特征.其中n为2.
- 将向量进行最长补齐:
- 作用: 为了不损失n-gram特征,使向量能够以矩阵形式作为模型输入.
- 举个栗子:
和第二步截断补齐类似:
序列列表: [[1, 2, 3, 4, 5, 6], [1, 3, 9], [2, 4, 6], [2, 3]]
以最长长度进行对齐后得到
新的序列列表: [[1, 2, 3, 4, 5, 6], [1, 3, 9, 0, 0, 0], [2, 4, 6, 0, 0, 0], [2, 3, 0, 0, 0, 0]]
- 进行词汇映射的代码分析过程:
# 导入用于对象保存与加载的joblib
from sklearn.externals import joblib
# 导入keras中的词汇映射器Tokenizer
from keras.preprocessing.text import Tokenizer
# 导入从样本csv到内存的get_data_labels函数
from data_analysis import get_data_labels
def word_map(csv_path, tokenizer_path, cut_num):
"""进行词汇映射,以训练数据的csv路径和映射器存储路径以及截断数为参数"""
# 使用get_data_labels函数获取简单处理后的训练数据和标签
train_data, train_labels = get_data_labels(csv_path)
# 进行正负样本均衡切割, 使其数量比例为1:1
train_data = train_data[:-cut_num]
train_labels = train_labels[:-cut_num]
# 实例化一个词汇映射器对象
t = Tokenizer(num_words=None, char_level=False)
# 使用映射器拟合现有文本数据
t.fit_on_texts(train_data)
# 使用joblib工具保存映射器
joblib.dump(t, tokenizer_path)
# 使用映射器转化现有文本数据
x_train = t.texts_to_sequences(train_data)
# 获得标签数据
y_train = train_labels
return x_train, y_train
- 代码位置: 代码将写在/data/django-uwsgi/text_labeled/model_train/movie_model_train.py中.
- 输入实例:
# 对应的样本csv路径
csv_path = "./movie/sample.csv"
# 词汇映射器保存的路径
tokenizer_path = "./movie/Tokenizer"
# 截断数
cut_num = 2525
- 输出效果:
# x_train
[[3480, 485, 9674, 979, 23, 67, 39, 1097, 432, 49, 27584, 205],
[17, 27585, 27586, 1355, 27587, 14019, 65, 100],
[2282, 2609, 7, 7616, 1897, 2302, 274, 1355, 2302, 20],
[57, 27588, 13601, 135, 586, 134, 4138], ...]
# y_train
[1 1 1 ... 0 0 0]
- 主要注释:
# 导入用于对象保存与加载的joblib
# 导入keras中的词汇映射器Tokenizer
# 导入从样本csv到内存的get_data_labels函数
"""进行词汇映射,以训练数据的csv路径和映射器存储路径以及截断数为参数"""
# 使用get_data_labels函数获取简单处理后的训练数据和标签
# 进行正负样本均衡切割, 使其数量比例为1:1
# 实例化一个词汇映射器对象
# 使用映射器拟合现有文本数据
# 使用joblib工具保存映射器
# 使用映射器转化现有文本数据
# 获得标签数据
- 向量截断对齐的代码分析过程:
from keras.preprocessing import sequence
# cutlen根据数据分析中句子长度分布,覆盖90%语料的最短长度.
cutlen = 60
def padding(x_train, cutlen):
return sequence.pad_sequences(x_train, cutlen)
- 代码位置: 代码将写在/data/django-uwsgi/text_labeled/model_train/movie_model_train.py中.
- 输入实例:
# 通过word_map函数获得的x_train
# 通过数据分析获得的截断长度
cutlen = 60
- 输出效果:
# 进行截断补齐后的矩阵x_train
[[ 0 0 0 ... 49 5576 5577]
[ 0 0 1682 ... 1 1682 7179]
[ 0 0 0 ... 148 10517 7183]
...
[ 0 0 0 ... 7245 1567 1731]
[ 0 0 0 ... 1872 364 20985]
[ 0 0 0 ... 10353 1207 20989]]
- 主要注释
# cutlen根据数据分析中句子长度分布,覆盖90%语料的最短长度.
- 加入n-gram特征过程的代码分析:
import numpy as np
# 根据样本集最大词汇数选择最大特征数,应大于样本集最大词汇数
max_features = 25000
# n-gram特征的范围,一般选择为2
ngram_range = 2
def create_ngram_set(input_list, ngram_value=2):
"""
从列表中提取n-gram特征
>>> create_ngram_set([1, 4, 9, 4, 1, 4], ngram_value=2)
{(4, 9), (4, 1), (1, 4), (9, 4)}
"""
return set(zip(*[input_list[i:] for i in range(ngram_value)]))
def get_ti_and_nmf(x_train, ti_path, ngram_range):
"""从训练数据中获得token_indice和新的max_features"""
# >>> token_indice = {(1, 3): 1337, (9, 2): 42, (4, 5): 2017}
# 创建一个盛装n-gram特征的集合.
ngram_set = set()
# 遍历每一个数值映射后的列表
for input_list in x_train:
# 遍历可能存在2-gram, 3-gram等
for i in range(2, ngram_range + 1):
# 获得对应的n-gram表示
set_of_ngram = create_ngram_set(input_list, ngram_value=i)
# 更新n-gram集合
ngram_set.update(set_of_ngram)
# 去除掉(0, 0)这个2-gram特征
ngram_set.discard(tuple([0]*ngram_range))
# 将n-gram特征映射成整数.
# 为了避免和之前的词汇特征冲突,n-gram产生的特征将从max_features+1开始
start_index = max_features + 1
# 得到对n-gram表示与对应特征值的字典
token_indice = {v: k + start_index for k, v in enumerate(ngram_set)}
# 将token_indice写入文件以便预测时使用
with open(ti_path, "w") as f:
f.write(str(token_indice))
# token_indice的反转字典,为了求解新的最大特征数
indice_token = {token_indice[k]: k for k in token_indice}
# 获得加入n-gram之后的最大特征数
new_max_features = np.max(list(indice_token.keys())) + 1
return token_indice, new_max_features
def add_ngram(sequences, token_indice, ngram_range=2):
"""
将n-gram特征加入到训练数据中
如: adding bi-gram
>>> sequences = [[1, 3, 4, 5], [1, 3, 7, 9, 2]]
>>> token_indice = {(1, 3): 1337, (9, 2): 42, (4, 5): 2017}
>>> add_ngram(sequences, token_indice, ngram_range=2)
[[1, 3, 4, 5, 1337, 2017], [1, 3, 7, 9, 2, 1337, 42]]
"""
new_sequences = []
# 遍历序列列表中的每一个元素作为input_list, 即代表一个句子的列表
for input_list in sequences:
# copy一个new_list
new_list = input_list[:].tolist()
# 遍历n-gram的value,至少从2开始
for ngram_value in range(2, ngram_range + 1):
# 遍历各个可能的n-gram长度
for i in range(len(new_list) - ngram_value + 1):
# 获得input_list中的n-gram表示
ngram = tuple(new_list[i:i + ngram_value])
# 如果在token_indice中,则追加相应的数值特征
if ngram in token_indice:
new_list.append(token_indice[ngram])
new_sequences.append(new_list)
return np.array(new_sequences)
- 代码位置: 代码将写在/data/django-uwsgi/text_labeled/model_train/movie_model_train.py中.
- 函数create_ngram_set(input_list, ngram_value=2):
- 输入实例:
input_list = [1, 4, 9, 4, 1, 4]
- 输出效果:
# 2-gram特征组成的集合
{(4, 1), (9, 4), (4, 9), (1, 4)}
- 函数get_ti_and_nmf(x_train, ti_path, ngram_range):
- 输入实例:
# 数据进行截断对齐后的矩阵x_train
# token_indice的保存路径
ti_path = "./movie/token_indice"
- 输出效果:
# token_indice 2-gram特征对应的数值
{(28, 1329): 143282, (413, 841): 143283,
(8731, 6757): 143284, (4975, 68): 143285,
(581, 9339): 143286, (744, 1819): 143287,
(16, 1368): 143288, (17661, 4177): 143289,
(20, 76): 143290, (495, 418): 143291, ...}
# new_max_features 新的最大特征数
143307
- 函数add_ngram(sequences, token_indice, ngram_range=2):
- 输入实例:
# 数据进行截断对齐后的矩阵x_train, 也就是输入sequences
# 由函数get_ti_and_nmf得到的token_indice
- 输出效果:
# 添加了n-gram特征的矩阵x_train
[list([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1296, 1086, 9, 2510, 2325, 1004, 668, 2990, 669, 482, 669, 335, 126063, 46370, 36768, 93632, 116281, 46593, 136403, 29955, 34254, 127711, 47353, 132158])
list([0, 0, 0, 0, 11, 4, 8280, 26, 2511, 2991, 528, 22, 411, 702, 11, 350, 8281, 604, 85, 1501, 468, 52, 11, 56, 3255, 104815, 38229, 35505, 67872, 28659, 50795, 140653, 113341, 65967, 78902, 57072, 108083, 29205, 115079, 61698, 48928, 42416, 46802, 110530, 99281, 40828])
...
]
- 主要注释:
# 根据样本集最大词汇数选择最大特征数,应大于样本集最大词汇数
# n-gram特征的范围,一般选择为2
"""
从列表中提取n-gram特征
>>> create_ngram_set([1, 4, 9, 4, 1, 4], ngram_value=2)
{(4, 9), (4, 1), (1, 4), (9, 4)}
"""
"""从训练数据中获得token_indice和新的max_features"""
# >>> token_indice = {(1, 3): 1337, (9, 2): 42, (4, 5): 2017}
# 创建一个盛装n-gram特征的集合.
# 遍历每一个数值映射后的列表
# 遍历可能存在2-gram, 3-gram等
# 获得对应的n-gram表示
# 更新n-gram集合
# 去除掉(0, 0)这个2-gram特征
# 将n-gram特征映射成整数.
# 为了避免和之前的词汇特征冲突,n-gram产生的特征将从max_features+1开始
# 得到对n-gram表示与对应特征值的字典
# 将token_indice写入文件以便预测时使用
# token_indice的反转字典,为了求解新的最大特征数
# 获得加入n-gram之后的最大特征数
"""
将n-gram特征加入到训练数据中
如: adding bi-gram
>>> sequences = [[1, 3, 4, 5], [1, 3, 7, 9, 2]]
>>> token_indice = {(1, 3): 1337, (9, 2): 42, (4, 5): 2017}
>>> add_ngram(sequences, token_indice, ngram_range=2)
[[1, 3, 4, 5, 1337, 2017], [1, 3, 7, 9, 2, 1337, 42]]
"""
# 遍历序列列表中的每一个元素作为input_list, 即代表一个句子的列表
# copy一个new_list
# 遍历n-gram的value,至少从2开始
# 遍历各个可能的n-gram长度
# 获得input_list中的n-gram表示
# 如果在token_indice中,则追加相应的数值特征
- 将向量进行最长补齐过程的代码分析:
def align(x_train):
"""用于向量按照最长长度进行补齐"""
# 获得所有句子长度的最大值
maxlen = max(list(map(lambda x: len(x), x_train)))
# 调用padding函数
x_train = padding(x_train, maxlen)
return x_train, maxlen
- 代码位置: 代码将写在/data/django-uwsgi/text_labeled/model_train/movie_model_train.py中.
- 输入实例:
# 由函数add_ngram输出的矩阵x_train
- 输出效果:
# 进行了最大长度补齐的矩阵x_train
[[ 0 0 0 ... 113541 36959 22941]
[ 0 0 1682 ... 42518 59855 25524]
[ 0 0 0 ... 75385 50810 68725]
...
[ 0 0 0 ... 97401 34490 77114]
[ 0 0 0 ... 21440 85555 32122]
[ 0 0 0 ... 56394 95696 45331]]
# 补齐的最大长度
119
- 主要注释:
"""用于向量按照最长长度进行补齐"""
# 获得所有句子长度的最大值
# 调用padding函数
- 练一练:
- 请同学们根据本小节学习的特征处理过程,在/data/django-uwsgi/text_labeled/model_train目录下, 创建star_model_train.py, beauty_model_train.py, fashion_model_train.py三个文件, 并根据数据分析的结果写出它们的特征处理流程.
-
小节总结:
- 学习了特征处理过程的四步曲:
- 第一步: 进行词汇映射
- 第二步: 将向量进行合适截断
- 第三步: 加入n-gram特征
- 第四步: 将向量进行最长补齐
- 学习并实现了进行词汇映射:
- 作用: 将分词列表中的每个词映射成数字.
- 函数: word_map
- 学习并实现了将向量进行合适截断补齐:
- 作用: 将映射后的句子向量进行截断补齐,以降低模型输入的特征维度,来防止过拟合.
- 函数: padding
- 学习并实现了加入n-gram特征:
- 作用: 将n-gram表示作为特征,能够补充特征中没有上下文关联的缺点,将有效帮助模型捕捉上下文的语义关联.
- 函数: create_ngram_set
- 函数: get_ti_and_nmf
- 函数: add_ngram
- 学习并实现了将向量进行最长补齐:
- 作用: 为了不损失n-gram特征,使向量能够以矩阵形式作为模型输入.
- 函数: align
- 学习了特征处理过程的四步曲:
3.4 构建模型结构
- 学习目标:
- 掌握fasttext模型结构中三个重要的层并使用它们构建模型.
- fasttext模型结构中三个重要的层:
- Embedding层
- GAP层(全局平均池化层)
- Dense + sigmoid层
- Embedding层:
- 层结构: 结构可以看作是一个矩阵,它的大小是语料的最大特征数(new_max_features)乘以我们预定义的embedding_dims,这个矩阵就相当于是由每一个特征拓展成embedding_dims后的表示.
- 层参数: 矩阵中的每一个数,都是模型需要求解的参数,因此Embedding层的参数总量是new_max_features x embedding_dims.
- 输入参数: new_max_features即最大特征数, embedding_dims即词嵌入维度, input_length即句子的最大长度.
- 输入形状: [None, input_length]
- 输出形状: [None, input_length, embedding_dims]
- 作用: 用向量表示每一个特征,在更高维度的映射空间捕捉词与词之间的关系.
- GAP层:
- 层结构: 本质上是对矩阵的一种计算方法,无结构.
- 层参数: 无
- 输入参数: 无
- 输入形状: [None, input_length, embedding_dims]
- 输出形状: [None, embedding_dims]
- 作用: 消减模型参数总量,防止过拟合.
- Dense + sigmoid层:
- 层结构: 具有个1个节点的一层全连接网络,最后的激活函数使用sigmoid.
- 层参数: 该节点中的w向量共50维,加上一个偏置b,共51个参数.
- 输入参数: 分别是该层的节点数以及使用的sigmoid函数.
- 输入形状: [None, embedding_dims]
- 输出形状: [None, 1]
- 作用: 将抽象的特征表示归一到指定的类别上,能够输出我们想要的0或者1的结果.
- 使用上面三个层构建fasttext模型的代码分析:
# 首先导入keras构建模型的必备工具包
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Embedding
from keras.layers import GlobalAveragePooling1D
# 定义词嵌入维度为50
embedding_dims = 50
def model_build(maxlen, new_max_features):
"""该函数用于模型结构构建"""
# 在函数中,首先初始化一个序列模型对象
model = Sequential()
# 然后首层使用Embedding层进行词向量映射
model.add(Embedding(new_max_features,
embedding_dims,
input_length=maxlen))
# 然后用构建全局平均池化层,减少模型参数,防止过拟合
model.add(GlobalAveragePooling1D())
# 最后构建全连接层 + sigmoid层来进行分类.
model.add(Dense(1, activation='sigmoid'))
return model
- 代码位置: 代码将写在/data/django-uwsgi/text_labeled/model_train/movie_model_train.py中.
- 输入实例:
# 最大对齐长度, 即输入矩阵中每条向量的长度
maxlen = 119
# 最大特征数, 即输入矩阵中元素的最大值
new_max_features = 143307
# 词嵌入的数量, 使用50维
embedding_dims = 50
- 输出效果:
# keras sequential的model对象:
<keras.engine.sequential.Sequential object at 0x7f67cc2bf208>
- 主要注释:
# 首先导入keras构建模型的必备工具包
# 定义词嵌入维度为50
"""该函数用于模型结构构建"""
# 在函数中,首先初始化一个序列模型对象
# 然后首层使用Embedding层进行词向量映射
# 然后用构建全局平均池化层,减少模型参数,防止过拟合
# 最后构建全连接层 + sigmoid层来进行分类.
- 练一练:
- 请同学们根据本小节学习的模型构建过程,在/data/django-uwsgi/text_labeled/model_train目录下的star_model_train.py, beauty_model_train.py, fashion_model_train.py三个文件中, 写出它们的模型结构构建函数.
-
小节总结:
- 学习了fasttext模型结构中三个重要的层:
- Embedding层
- GAP层(全局平均池化层)
- Dense + sigmoid层
- 学习了Embedding层:
- 层结构: 结构可以看作是一个矩阵,它的大小是语料的最大特征数(new_max_features)乘以我们预定义的embedding_dims,这个矩阵就相当于是由每一个特征拓展成embedding_dims后的表示.
- 层参数: 矩阵中的每一个数,都是模型需要求解的参数,因此Embedding层的参数总量是new_max_features x embedding_dims.
- 输入参数: new_max_features即最大特征数, embedding_dims即词嵌入维度, input_length即句子的最大长度.
- 输入形状: [None, input_length]
- 输出形状: [None, input_length, embedding_dims]
- 作用: 用向量表示每一个特征,在更高维度的映射空间捕捉词与词之间的关系.
-
学习了GAP层:
- 层结构: 本质上是对矩阵的一种计算方法,无结构.
- 层参数: 无
- 输入参数: 无
- 输入形状: [None, input_length, embedding_dims]
- 输出形状: [None, embedding_dims]
- 作用: 消减模型参数总量,防止过拟合.
-
学习了Dense + sigmoid层:
- 层结构: 具有个1个节点的一层全连接网络,最后的激活函数使用sigmoid.
- 层参数: 该节点中的w向量共50维,加上一个偏置b,共51个参数.
- 输入参数: 分别是该层的节点数以及使用的sigmoid函数.
- 输入形状: [None, embedding_dims]
- 输出形状: [None, 1]
- 作用: 将抽象的特征表示归一到指定的类别上,能够输出我们想要的0或者1的结果.
- 学习并实现了使用上面三个层构建fasttext模型.
- 函数: model_build
- 学习了fasttext模型结构中三个重要的层:
3.5 选取损失函数和优化方法
- 学习目标:
- 掌握fasttext模型选取的损失函数与优化方法及其实现.
- fasttext模型选取的损失函数:
- 二分类交叉熵损失函数
- fasttext模型选取的优化方法:
- Adam
-
二分类交叉熵损失:
- 作用: 用于描述模型预测值与真实值的差距大小.
-
形式:

-
优势: 作为一种优化后的损失函数,能够避免均方误差损失的在处理sigmoid函数时收敛到趋于1时,梯度非常平缓,又因为使用的优化方法往往基于梯度下降,所以出现”学习迟缓”的现象.为了避免该现象,交叉熵的形式应运而生。
- Adam优化方法:
- 作用: 求损失函数最优解的方法.
- 优势: 结合Adagrad与RSMProp的算法特点, 对梯度的一阶矩估计(即梯度的均值)和二阶矩估计(即梯度的未中心化的方差)进行综合考虑,计算出更新步长. 学习率可自由调节.
- 实现在选取损失函数与优化方法的代码分析过程:
def model_compile(model):
"""用于选取模型的损失函数和优化方法"""
# 使用model自带的compile方法,选择预定义好的二分类交叉熵损失函数,Adam优化方法,以及准确率评估指标.
model.compile(loss='binary_crossentropy',
optimizer='adam',
metrics=['accuracy'])
return model
- 代码位置: 代码将写在/data/django-uwsgi/text_labeled/model_train/movie_model_train.py中.
- 输入实例:
# 由model_build获得的keras sequential的model对象
<keras.engine.sequential.Sequential object at 0x7f67cc2bf208>
- 输出效果:
# 加入了损失函数和优化方法的keras sequential的model对象
<keras.engine.sequential.Sequential object at 0x7f3f09baf1d0>
- 主要注释:
"""用于选取模型的损失函数和优化方法"""
# 使用model自带的compile方法,选择预定义好的二分类交叉熵损失函数,Adam优化方法,以及准确率评估指标.
- 练一练:
- 请同学们根据本小节学习的选取损失函数和优化方法的过程, 在/data/django-uwsgi/text_labeled/model_train目录下的star_model_train.py, beauty_model_train.py, fashion_model_train.py三个文件中, 写出它们的选取损失函数和优化方法的函数.
-
小节总结:
- fasttext模型选取的损失函数:
- 二分类交叉熵损失函数
- fasttext模型选取的优化方法:
- Adam
- 二分类交叉熵损失:
- 作用: 用于描述模型预测值与真实值的差距大小.
- 形式:

- 优势: 作为一种优化后的损失函数,能够避免均方误差损失的在处理sigmoid函数时收敛到趋于1时,梯度非常平缓,又因为使用的优化方法往往基于梯度下降,所以出现”学习迟缓”的现象.为了避免该现象,交叉熵的形式应运而生。
- Adam优化方法:
- 作用: 求损失函数最优解的方法.
- 优势: 结合Adagrad与RSMProp的算法特点, 对梯度的一阶矩估计(即梯度的均值)和二阶矩估计(即梯度的未中心化的方差)进行综合考虑,计算出更新步长. 学习率可自由调节.
- 学习并实现了在模型中选取损失函数与优化方法.
- 函数: model_compile
- fasttext模型选取的损失函数:
3.6 进行模型训练
- 学习目标:
- 掌握实现模型训练和绘制准曲率和损失对照曲线的过程.
- 学习如何根据对照曲线判断过拟合及其如何防止过拟合.
- 模型训练和绘制准曲率和损失对照曲线过程的代码分析:
# 导入作图工具包matplotlib
import matplotlib.pyplot as plt
# batch_size是每次进行参数更新的样本数量
batch_size = 32
# epochs将全部数据遍历训练的次数
epochs = 40
def model_fit(model, x_train, y_train):
"""用于模型训练"""
history = model.fit(x_train,
y_train,
batch_size=batch_size,
epochs=epochs,
# validation_split表示将全部训练数据的多少划分为验证集.
validation_split=0.1)
return history
def plot_loss_acc(history, acc_png_path, loss_png_path):
"""用于绘制模型的损失和acc对照曲线, 以模型训练历史为参数"""
# 首先获得模型训练历史字典,
# 形如{'val_loss': [0.8132099324259264, ..., 0.8765081824927494],
# 'val_acc': [0.029094827586206896,...,0.13038793103448276],
# 'loss': [0.6650978644232184,..., 0.5267722122513928],
# 'acc': [0.5803400383141762, ...,0.8469827586206896]}
history_dict = history.history
# 取出需要的的各个key对应的value,准备作为纵坐标
acc = history_dict['acc']
val_acc = history_dict['val_acc']
loss = history_dict['loss']
val_loss = history_dict['val_loss']
# 取epochs的递增列表作为横坐标
epochs = range(1, len(acc) + 1)
# 绘制训练准确率的点图
plt.plot(epochs, acc, 'bo', label='Training acc')
# 绘制验证准确率的线图
plt.plot(epochs, val_acc, 'b', label='Validation acc')
# 增加标题
plt.title('Training and validation accuracy')
# 增加横坐标名字
plt.xlabel('Epochs')
# 增加纵坐标名字
plt.ylabel('Accuracy')
# 将上面的图放在一块画板中
plt.legend()
# 保存图片
plt.savefig(acc_png_path)
# 清空面板
plt.clf()
# 绘制训练损失的点图
plt.plot(epochs, loss, 'bo', label='Training loss')
# 绘制验证损失的线图
plt.plot(epochs, val_loss, 'b', label='Validation loss')
# 添加标题
plt.title('Training and validation loss')
# 添加横坐标名字
plt.xlabel('Epochs')
# 添加纵坐标名字
plt.ylabel('Loss')
# 把两张图放在一起
plt.legend()
# 保存图片
plt.savefig(loss_png_path)
- 代码位置: 代码将写在/data/django-uwsgi/text_labeled/model_train/movie_model_train.py中.
- 函数model_fit(model, x_train, y_train):
- 输入实例:
# 加入了损失函数和优化方法的keras sequential的model对象
<keras.engine.sequential.Sequential object at 0x7f3f09baf1d0>
# 经过特征处理的x_train以及对应y_train
- 输出效果:
# 模型训练日志
Epoch 3/40
5299/5299 [==============================] - 7s 1ms/step - loss: 0.4094 - acc: 0.7998 - val_loss: 0.9937 - val_acc: 0.1800
Epoch 4/40
5299/5299 [==============================] - 7s 1ms/step - loss: 0.3185 - acc: 0.8498 - val_loss: 0.8025 - val_acc: 0.3548
Epoch 5/40
5299/5299 [==============================] - 7s 1ms/step - loss: 0.2379 - acc: 0.9136 - val_loss: 0.7550 - val_acc: 0.4482
Epoch 6/40
5299/5299 [==============================] - 7s 1ms/step - loss: 0.1779 - acc: 0.9500 - val_loss: 0.6113 - val_acc: 0.5857
Epoch 7/40
5299/5299 [==============================] - 7s 1ms/step - loss: 0.1355 - acc: 0.9726 - val_loss: 0.5836 - val_acc: 0.6214
Epoch 8/40
5299/5299 [==============================] - 7s 1ms/step - loss: 0.1056 - acc: 0.9826 - val_loss: 0.4837 - val_acc: 0.6893
Epoch 9/40
5299/5299 [==============================] - 7s 1ms/step - loss: 0.0844 - acc: 0.9870 - val_loss: 0.5271 - val_acc: 0.6570
Epoch 10/40
4384/5299 [=======================>......] - ETA: 1s - loss: 0.0691 - acc: 0.991
# 模型训练的历史对象
<keras.callbacks.History object at 0x7f75135f55f8>
- 函数plot_loss_acc(history, acc_png_path, loss_png_path):
- 输入实例:
# 模型训练的历史对象history
# 准确率对照曲线存储路径
acc_png_path = "./movie/acc.png"
# 损失对照曲线存储路径
loss_png_path = "./movie/loss.png"
- 输出效果:
# 在./movie/路径下两张图片, acc.png和loss.png, 分别是训练与验证的准确率和损失对照曲线
- 训练与验证的损失对照曲线:

- 通过损失对照曲线判断模型是否收敛:
- 当双损失曲线都在下降时,说明模型正在收敛, 大部分情况下,模型都会收敛.
- 训练与验证的准确率对照曲线:

- 通过准确率对照曲线判断过拟合:
- 当训练准确率平缓或上升而验证准确率开始平缓或下降时,在这个点处开始出现过拟合现象.
- 如何防止过拟合:
- 通过对比验证集与训练集准确曲线,在验证acc不在上升的轮数时停止训练.
- 进入服务器中修改epochs的值为10-20之间数值, 重新训练即可.
- 主要注释:
# 导入作图工具包matplotlib
# batch_size是每次进行参数更新的样本数量
# epochs将全部数据遍历训练的次数
"""用于模型训练"""
# validation_split表示将全部训练数据的多少划分为验证集.
"""用于绘制模型的损失和acc对照曲线, 以模型训练历史为参数"""
# 首先获得模型训练历史字典,
# 形如{'val_loss': [0.8132099324259264, ..., 0.8765081824927494],
# 'val_acc': [0.029094827586206896,...,0.13038793103448276],
# 'loss': [0.6650978644232184,..., 0.5267722122513928],
# 'acc': [0.5803400383141762, ...,0.8469827586206896]}
# 取出需要的的各个key对应的value,准备作为纵坐标
# 取epochs的递增列表作为横坐标
# 绘制训练准确率的点图
# 绘制验证准确率的线图
# 增加标题
# 增加横坐标名字
# 增加纵坐标名字
# 将上面的图放在一块画板中
# 保存图片
# 清空面板
# 绘制训练损失的点图
# 绘制验证损失的线图
# 添加标题
# 添加横坐标名字
# 添加纵坐标名字
# 把两张图放在一起
# 保存图片
- 练一练:
- 请同学们根据本小节学习的模型训练过程, 在/data/django-uwsgi/text_labeled/model_train目录下的star_model_train.py, beauty_model_train.py, fashion_model_train.py三个文件中, 写出它们的模型训练函数, 并进行训练, 得到损失以及准确率对比曲线.
-
小节总结:
-
学习了实现模型训练和绘制准确率和损失对照曲线的过程.
- 函数: model_fit
- 函数: plot_loss_acc
-
学习了通过损失对照曲线判断模型是否收敛:
- 当双损失曲线都在下降时,说明模型正在收敛, 大部分情况下,模型都会收敛.
-
学习了通过准确率对照曲线判断过拟合:
- 当训练准确率平缓或上升而验证准确率开始平缓或下降时,在这个点处开始出现过拟合现象.
-
学习了如何防止过拟合:
- 通过对比验证集与训练集准确曲线,在验证acc不在上升的轮数时停止训练.
-
3.7 模型保存与加载
- 学习目标:
- 掌握实现模型保存与加载的过程.
- 模型保存与加载过程的代码分析:
from keras.models import load_model
def model_save(save_path, model):
"""模型保存函数"""
# 使用model.save对模型进行保存.
model.save(save_path)
return
def model_load(save_path, sample):
"""模型加载与预测函数"""
# 使用load_model方法进行加载
model = load_model(save_path)
# 使用predict方法进行预测
result = model.predict(sample)
return result
- 代码位置: 代码将写在/data/django-uwsgi/text_labeled/model_train/movie_model_train.py中.
- 函数model_save(save_path, model):
- 输入实例:
# 模型的保存路径
save_path = "./movie/model.h5"
# 训练之后的model对象
- 输出效果:
# 在./movie路径下, 获得一个model.h5
- 函数model_load(save_path, sample):
- 输入实例:
# 模型保存的路径
save_path = "./movie/model.h5"
# 一个实例: 训练数据的第一条
sample = np.array([x_train[0]])
- 输出效果:
# 一个训练实例的预测结果
[[0.9996284]]
- 主要注释:
"""模型保存函数"""
# 使用model.save对模型进行保存.
"""模型加载与预测函数"""
# 使用load_model方法进行加载
# 使用predict方法进行预测
- 练一练:
- 请同学们根据本小节学习的模型保存与加载过程, 在/data/django-uwsgi/text_labeled/model_train目录下的star_model_train.py, beauty_model_train.py, fashion_model_train.py三个文件中, 写出它们的模型保存 与加载函数, 得到对应的h5模型.
- 小节总结:
- 学会了实现模型保存与加载的过程.
- 函数: model_save
- 函数: model_load
- 学会了实现模型保存与加载的过程.
本章总结
-
第1小节: 获取训练语料
- 学习了获取训练语料过程的三步曲:
- 第一步: 明确原始数据来源.
- 第二步: 定义正负样本.
- 第三步: 提取正负样本语料.
- 学习了明确原始数据来源:
- 公司内容生产小组提供的各种类型的文章.
- 学习了定义正负样本:
- 将文章中的每一条句子作为该类别的正样本; 将其他类别文章中的每一条句子作为负样本.
- 学习并实现了提取正负样本:
- 先提取正样本语料,再在正样本基础上,提取正负样本语料.
- 学习了获取训练语料过程的三步曲:
-
第2小节: 进行数据分析
- 学习了数据分析过程的四步曲:
- 第一步: 获取正负样本的分词列表和对应的标签.
- 第二步: 获取正负标签数量分布.
- 第三步: 获取句子长度分布.
- 第四步: 获取常见词频分布.
- 学习并实现了获取正负样本的分词列表和对应的标签.
- 作用: 为进行可视化数据分析作数据准备.
- 学习并实现了获取正负标签数量分布.
- 作用: 用于帮助调整正负样本比例.
- 学习并实现了获取句子长度分布.
- 作用: 用于帮助判断句子合理的截断长度.
- 学习并实现了获取常见词频分布.
- 作用: 指导之后模型超参数max_features的选择和初步评估数据质量.
- 学习了数据分析过程的四步曲:
-
第3小节: 特征处理
- 学习了特征处理过程的四步曲:
- 第一步: 进行词汇映射
- 第二步: 将向量进行合适截断
- 第三步: 加入n-gram特征
- 第四步: 将向量进行最长补齐
- 学习并实现了进行词汇映射:
- 作用: 将分词列表中的每个词映射成数字.
- 学习并实现了将向量进行合适截断:
- 作用: 将映射后的句子向量进行截断,以降低模型输入的特征维度,来防止过拟合.
- 学习并实现了加入n-gram特征:
- 作用: 将n-gram表示作为特征,能够补充特征中没有上下文中关联的缺点,将有效帮助模型捕捉上下文的语义关联.
- 学习并实现了将向量进行最长补齐:
- 作用: 为了不损失n-gram特征,使向量能够以矩阵形式作为模型输入.
- 学习了特征处理过程的四步曲:
-
第4小节: 构建模型结构
- 学习了模型结构中三个重要的层:
- Embedding层
- GAP层(全局平均池化层)
- Dense + sigmoid层
- 学习了Embedding层:
- 层结构: 结构可以看作是一个矩阵,它的大小是语料的有效词汇总数(max_features)乘以我们预定义的embedding_dims,这个矩阵就相当于是由每一个特征拓展成embedding_dims后的表示.
- 层参数: 矩阵中的每一个数,一般模型需要求解的参数,因此Embedding层的参数总量是max_features x embedding_dims.
- 输入参数: max_features即最大特征数, embedding_dims即词嵌入维度, input_length即句子的最大长度.
- 输入形状: [None, input_length]
- 输出形状: [None, input_length, embedding_dims]
- 作用: 用向量表示每一个特征,在更高维度的映射空间捕捉词与词之间的关系.
- 学习了GAP层:
- 层结构: 本质上是对矩阵的一种计算方法,无结构.
- 层参数: 无
- 输入参数: 无
- 输入形状: [None, input_length, embedding_dims]
- 输出形状: [None, embedding_dims]
- 作用: 消减模型参数总量,防止过拟合.
- 学习了Dense + sigmoid层:
- 层结构: 具有个1个节点的一层全连接网络,最后的激活函数使用sigmoid.
- 层参数: 该节点中的w向量共50维,加上一个偏置b,共51个参数.
- 输入参数: 分别是该层的节点数以及使用的sigmoid函数.
- 输入形状: [None, embedding_dims]
- 输出形状: [None, 1]
- 作用: 将抽象的特征表示归一到指定的类别上,能够输出我们想要的0或者1的结果.
- 学习并实现了使用上面三个层构建fasttext模型.
- 学习了模型结构中三个重要的层:
-
第5小节: 选取损失函数和优化方法
-
fasttext模型选取的损失函数:
- 二分类交叉熵损失函数
-
fasttext模型选取的优化方法:
- Adam
- 二分类交叉熵损失:
- 作用: 用于描述模型预测值与真实值的差距大小.
- 形式:

- 优势: 作为一种优化后的损失函数,能够避免均方误差损失的在处理sigmoid函数时收敛到趋于1时,梯度非常平缓,又因为使用的优化方法往往基于梯度下降,所以出现”学习迟缓”的现象.为了避免该现象,交叉熵的形式应运而生。
- Adam优化方法:
- 作用: 求损失函数最优解的方法.
- 优势: 结合Adagrad与RSMProp的算法特点, 对梯度的一阶矩估计(即梯度的均值)和二阶矩估计(即梯度的未中心化的方差)进行综合考虑,计算出更新步长. 学习率可自由调节.
- 学习并实现了在模型中选取损失函数与优化方法.
-
-
第6小节: 进行模型训练
- 学习了实现模型训练和绘制准曲率和损失对照曲线的过程.
- 学习了通过准确率对照曲线判断过拟合:
- 当训练准确率上升而验证准确率开始平缓或下降时,在这个点处开始出现过拟合现象.