Alink教程(Java版)

第28.2节 BERT文本向量化


BertTextEmbedding为Alink BERT文本向量化的Pipeline组件,其输入为原始的文本数据,不需要事先做分词、构造特征等操作;其结果为向量,可以作为逻辑回归等分类器的向量特征。Pipeline的构建和训练代码如下:

new Pipeline()
	.add(
		new Imputer()
			.setSelectedCols("review")
			.setStrategy("value")
			.setFillValue("null")
	)
	.add(
		new BertTextEmbedding()
			.setBertModelName("Base-Chinese")
			.setSelectedCol("review")
			.setOutputCol("vec")
	)
	.add(
		new LogisticRegression()
			.setVectorCol("vec")
			.setLabelCol("label")
			.setPredictionCol("pred")
			.setPredictionDetailCol("pred_info")
	)
	.fit(train_set)
	.save(DATA_DIR + "bert_vec_lr_pipeline_model.ak", true);
BatchOperator.execute();

由于数据中有缺失值,在运行BertTextEmbedding之前,需要进行缺失值填充,填充为字符串值“null”。通过Pipeline的fit()方法,可以得到整个流程的模型(PipelineModel),并将模型保存到文件“bert_pipeline_model.ak”。

导入上面训练的PipelineModel模型后,可以调用transform方法对批式/流式数据进行预测,最后,使用 EvalBinaryClassBatchOp组件进行二分类模型评估。具体代码如下:

PipelineModel.load(DATA_DIR + "bert_vec_lr_pipeline_model.ak")
	.transform(test_set)
	.lazyPrint(5)
	.link(
		new EvalBinaryClassBatchOp()
			.setLabelCol("label")
			.setPredictionDetailCol("pred_info")
			.lazyPrintMetrics("BERT_VEC_LR")
	);
BatchOperator.execute();

打印的5条预测数据如下,其中vec一列内容较多,只保留了开头和结尾的数值。

label|review|vec|pred|pred_info
-----|------|---|----|---------
0|房间还可以,设施简单,不像是四星标准。早餐还算丰富,不过要早去。前台接待、结算速度和态度还不错。后院停车场很有特点,一晚上有两台车被划伤,真恐怖,最关键的是保安及小头目还不想认帐,气愤啊|0.3820178 0.069895454 …… -0.22388023|1|{"0":"0.0037420480419082525","1":"0.9962579519580917"}
1|498元的价格不是很厚道,但位置还是可以,预定时携程说酒店要信用卡担保,担保后到了酒店时,酒店说我预定的标准间没有了,免费个给我升级到套房。房间很大,感觉不错。|0.53674614 0.09085431 …… -0.13892585|1|{"0":"0.008692634488966844","1":"0.9913073655110332"}
0|酒店位置比地址说明上的偏多了,接近市郊,路边连个明确的牌子都没打出,失望。酒店外观还算过得去,大堂却很一般(有点像旅社的大堂),走廊和房间的装修都较陈旧,和携程发布的图片相去甚远。洗漱用品配备简陋,大概也就是个三星的级别。同等价位,比起曾经住过的天河购物中心附近的广州皇家国际饭店无论装修还是服务,甚至连自助早,都要差很多。订房的时候总想不起皇家国际饭店的具体名字,才定了这个饭店,真是悔啊。以后到广州不会再选择这里,绝不~~|0.44225135 -0.009138018 …… -0.16537604|0|{"0":"0.9845174932033899","1":"0.015482506796610118"}
1|确实是有不断的进步,前台\礼宾的服务有了明显提高,以后还会继续住在这里!特别感谢的是这次入住商务间收到了一小盒6瓶装食醋口服液,当天晚上喝掉一瓶,余下的原想带走的,由于特殊时期火车上不许带液体,就赠送给了服务员。但还是要谢谢酒店!|0.6196639 -0.12604691 …… -0.12273903|1|{"0":"1.2431361776610306E-4","1":"0.9998756863822339"}
1|1.希望房间的改造加快进度,改造完房间不错,没有改造好的房间的确是三星的感觉.2.早餐很有特色,很不错.|0.45537332 -0.051367223 …… -0.26098222|1|{"0":"3.130274830054347E-5","1":"0.9999686972516995"}


模型评估指标如下,比上节的LR模型略好。

-------------------------------- Metrics: --------------------------------
Auc:0.9198	Accuracy:0.861	Precision:0.8919	Recall:0.9086	F1:0.9002	LogLoss:0.4421
|Pred\Real|  1|  0|
|---------|---|---|
|        1|487| 59|
|        0| 49|182|

使用LocalPredictor导入前面训练的PipelineModel,可以被集成到预测服务中。具体代码如下,首先是通过导入PipelineModel,构建LocalPredictor实例。

LocalPredictor localPredictor
	= new LocalPredictor(DATA_DIR + "bert_vec_lr_pipeline_model.ak", "review string");


使用 getOutputSchema 方法获得当前预测输出结果的 Schema,代码如下:

System.out.println(localPredictor.getOutputSchema());


运行结果如下,第一列为review,预测的文本内容;第二列为BERT文本向量化组件计算出的向量列;第三列为pred,预测结果;第四列为pred_info,预测详情信息。

root
 |-- review: STRING
 |-- vec: STRING
 |-- pred: INT
 |-- pred_info: STRING


下面使用localPredictor进行预测,代码如下。构造了两条预测文本数据,使用localPredictor的map方法进行预测,输入数据的类型为Row,这里使用Row.of方法进行转换。

final int index_pred = TableUtil.findColIndex(localPredictor.getOutputSchema(), "pred");

String[] reviews = new String[] {
	"硬件不错,服务态度也不错,下次到附近的话还会选择住这里",
	"房间还比较干净,交通方便,离外滩很近.但外面声音太大,休息不好",
};

for (String review : reviews) {
	Object[] result = localPredictor.predict(review);
	System.out.println("Pred Result : " + result[index_pred] + " @ " + review);
}

注意:预测结果的类型为Row,即为一行数据,包含多个数据字段。可以通过TableUtil.findColIndex方法,确定预测结果“pred”列的位置索引值index_pred,从Row类型变量result中使用getField方法获取索引值为index_pred的数据字段,就是预测结果。

运行结果为

Pred Result : 1 @ 硬件不错,服务态度也不错,下次到附近的话还会选择住这里
Pred Result : 1 @ 房间还比较干净,交通方便,离外滩很近.但外面声音太大,休息不好