**本文是一份大作业,目的是跑通博主appleyk的基于电影知识图谱的智能问答系统,相关信息可以去

https://blog.csdn.net/Appleyk/article/details/80422055?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

处查看**
1, Neo4j的相关操作
Neo4j在处理节点(对象)关系这方面性能比较棒,而且查询语句简单,更容易构建我们的电影知识图谱,本系列实验中,参考博主采用neo4j来构建项目,所以我们自然需要安装Neo4j来储存对对象的数据处理。
首先,我们需要下载Neo4j的安装包,由于从官网下载速度太慢,我们选择了网上国内的资源,下载的版本位3.5.5。
下载后解压到任意文件夹,下面是本次作业中我的Neo4j的安装路径:E:\neo4j-community-3.5.5
之后我们需要将Neo4j的环境变量配置好:
编辑完路径后,测试一下是否安装成功:

 安装成功后我们就可以将准备好了CSV数据文件导入到Neo4j中:如图我们已经成功地将数据导入Neo4j的默认路径了,下面需要将数据导入Neo4j中:

找到neo4j的安装路径,并在D:\neo4j-community-3.4.0\目录下创建import目录
完整路径如下D:\neo4j-community-3.4.0\import
因为neo4j支持导入csv文件,其默认目录入口是 …\import

//导入节点 电影类型 == 注意类型转换
LOAD CSV WITH HEADERS FROM “file:///genre.csv” AS line
MERGE (p:Genre{gid:toInteger(line.gid),name:line.gname})

//导入节点 演员信息
LOAD CSV WITH HEADERS FROM ‘file:///person.csv’ AS line
MERGE (p:Person { pid:toInteger(line.pid),birth:line.birth,
death:line.death,name:line.name,
biography:line.biography,
birthplace:line.birthplace})

// 导入节点 电影信息
LOAD CSV WITH HEADERS FROM “file:///movie.csv” AS line
MERGE (p:Movie{mid:toInteger(line.mid),title:line.title,introduction:line.introduction,
rating:toFloat(line.rating),releasedate:line.releasedate})

// 导入关系 actedin 电影是谁参演的 1对多
LOAD CSV WITH HEADERS FROM “file:///person_to_movie.csv” AS line
match (from:Person{pid:toInteger(line.pid)}),(to:Movie{mid:toInteger(line.mid)})
merge (from)-[r:actedin{pid:toInteger(line.pid),mid:toInteger(line.mid)}]->(to)

//导入关系 电影是什么类型 == 1对多
LOAD CSV WITH HEADERS FROM “file:///movie_to_genre.csv” AS line
match (from:Movie{mid:toInteger(line.mid)}),(to:Genre{gid:toInteger(line.gid)})
merge (from)-[r:is{mid:toInteger(line.mid),gid:toInteger(line.gid)}]->(to)

导入完成后:测试一下:match (n)-[r:actedin]-(b) return n,r,b limit 10

2, Spark环境搭建
步骤如下:
====第一步:安装Java1.8 JDK,配置JAVA_HOME环境变量

下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk-netbeans-jsp-142931.html

JAVA_HOME = D:\java
path = …;%JAVA_HOME%\bin;

====第二步:安装Scala环境【Spark依赖于scala语言环境】

选择tools目录下的scala-2.10.5.msi【windows下可执行安装】

注意:安装的目录名一定不能带空格,可以再D盘下创建安装根目录D:\scala

安装完成后配置 scala环境变量

SCALA_HOME = D:\scala
path = …;%SCALA_HOME%\bin;

===第三步:配置hadoop环境

选择tools目录下的hadoop-3.0.1.tar.gz,解压,并重命名目录,最终路径形式为

D:\hadoop

解压完成后,配置hadoop系统环境变量

HADOOP_HOME=D:\hadoop
path=…;%HADOOP_HOME%\bin;

hadoop这里需要注意一个地方,需要将tools目录下的winutils.exe文件拷贝到hadoop的bin目录下

如果没有这个否则会出现 java.io.IOException: Could not locate executable null\bin\winutils.exe in the Hadoop binaries.

拷贝后,在D盘新建一个二级目录 D:\tmp\hive

然后cmd切换到D:\hadoop\bin\目录下,并执行
D:\hadoop\bin>winutils.exe chmod 777 D:/tmp/hive == 赋权限

===第四步:安装spark并配置环境变量
选择tools目录下的spark-2.3.0-bin-hadoop2.6.tgz,解压,并重命名目录,最终路径形式为

D:\spark

然后和上面雷同,配置spark环境变量

随后任意目录cmd执行 spark-shell,即可验证spark环境是否搭建成功。

3, Hanlp环境搭建
下载hanlp的各种东西
方法1.maven方法,下载一个0配置即可。【但是我不太会用】
方法2:先下载hanlp-1.2.8.jar这个jar包
http://hanlp.linrunsoft.com/services.html
再下载data.zip这个数据包,可以选择,选择下载标准数据or迷你数据or全部数据。大小不同。我下的是标准版的。40M
再下载hanlp.properties这个是一个以properties结尾的一个文件,我之前从来没见过,不过可以用txt打开。
第四步:把下载的这些东西导入到eclipse里面去。构建路径
1.把jar包导入到eclipse的lib目录下
http://jingyan.baidu.com/article/ca41422fc76c4a1eae99ed9f.html
2.自己在src里面创建一个包,在包里面创建一个类。包会在我设置的根目录D:/下面,类名称首字母必须大写?【貌似不大写的话,会被否决】
3.把data包解压,然后放在一个自己喜欢的路径【我的路径是D://py/】然后,在hanlp.properties这个文件里,把root修改为data存放的上一级目录。
4.把hanlp.properties拖动到src这个目录下。
这里我们用的是idea里面的maven,所以基本不用手动导入

它会自动导入你需要的包,非常实用,方便。
但这里需要注意的是,hanlp的词库需要你自己放到本机的正确位置,并且将需要的自定义词库放进去。

上图的红框内就是需要的自定义词库。

4, 项目运行
做完这些其实你为项目进行的环境配置已经足够了,那么我们可以在博主给的github上下载我们的智能问答系统运行一下。

有了上图SPRING的标志就代表着我们的项目正常运行了。
在运行之前还需要将之前配置的Neo4j启动

还需要在idea中将tomcat配置好,因为我们的前端是在web服务器上来进行的。

接下来就可以区localhost:8080看一下我们的项目是否运行成功:

可以看到我们的项目已经跑起来了。

5, 算法原理理解:
那么正常的跑起来项目我们已经做到了,但是更重要的是我们要理解这个问答系统的算法原理。
其中最重要的一点就是我们需要理解机器是如何识别我们的问题,并且分类理解我们的问题如何解答呢?
事实上为了实现上面这点,在这个项目中运用的是Hanlp+朴素贝叶斯分类器来进行机器学习,用部分数据训练,这样机器就可以识别我们的问题到底属于哪一类。
Hanlp分词就用hanlp自带的方法,这里我们编译一个小的测试范例来熟悉一下hanlp的工作原理。
下面附上理解hanlp的测试代码,并附上注释:
import java.util.ArrayList;
import java.util.List;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.mllib.classification.NaiveBayes;
import org.apache.spark.mllib.classification.NaiveBayesModel;
import org.apache.spark.mllib.linalg.Vector;
import org.apache.spark.mllib.linalg.Vectors;
import org.apache.spark.mllib.regression.LabeledPoint;
import org.junit.Test;

import com.appleyk.process.ModelProcess;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.dictionary.CustomDictionary;
import com.hankcs.hanlp.seg.Segment;
import com.hankcs.hanlp.seg.common.Term;

public class HanLPTest {

@Test
public void TestA(){
String lineStr = “明天虽然会下雨,但是我还是会看周杰伦的演唱会。”;
try{
Segment segment = HanLP.newSegment();
segment.enableCustomDictionary(true);
/**
* 自定义分词+词性
*/
CustomDictionary.add(“虽然会”,“ng 0”);
List seg = segment.seg(lineStr);
for (Term term : seg) {
System.out.println(term.toString());
}
}catch(Exception ex){
System.out.println(ex.getClass()+","+ex.getMessage());
}
}
@Test
public void TestB(){
HanLP.Config.Normalization = true;
CustomDictionary.insert(“爱听4G”, “nz 1000”);
System.out.println(HanLP.segment(“爱听4g”));
System.out.println(HanLP.segment(“爱听4G”));
System.out.println(HanLP.segment(“爱听4G”));
System.out.println(HanLP.segment(“爱听4G”));
System.out.println(HanLP.segment(“愛聽4G”));
}

@Test
public void TestC() throws Exception{
ModelProcess query = new ModelProcess(“D:/HanLP/data”);
String[] questionArr = new String[] {“卧虎藏龙的分数是多少”};
for(String que: questionArr){
ArrayList question = query.analyQuery(que);
System.err.println(question);
}
}

@Test
public void TestRDD(){

  SparkConf conf = new SparkConf().setAppName("NaiveBayesTest").setMaster("local[*]");JavaSparkContext sc = new JavaSparkContext(conf);/*** MLlib的本地向量主要分为两种,DenseVector和SparseVector* 前者是用来保存稠密向量,后者是用来保存稀疏向量     *//*** 两种方式分别创建向量  == 其实创建稀疏向量的方式有两种,本文只讲一种* (1.0, 0.0, 2.0)* (2.0, 3.0, 0.0)*///稠密向量 == 连续的Vector dense = Vectors.dense(1.0,0.0,2.0);System.out.println(dense);//稀疏向量 == 间隔的、指定的,未指定位置的向量值默认 = 0.0int len = 3;int[] index = new int[]{0,1};double[] values = new double[]{2.0,3.0};Vector sparse = Vectors.sparse(len, index, values);/*** labeled point 是一个局部向量,要么是密集型的要么是稀疏型的* 用一个label/response进行关联* 在MLlib里,labeled points 被用来监督学习算法* 我们使用一个double数来存储一个label,因此我们能够使用labeled points进行回归和分类* 在二进制分类里,一个label可以是 0(负数)或者 1(正数)* 在多级分类中,labels可以是class的索引,从0开始:0,1,2,......*///训练集生成 ,规定数据结构为LabeledPoint == 构建方式:稠密向量模式  ,1.0:类别编号LabeledPoint train_one = new LabeledPoint(1.0,dense);  //(1.0, 0.0, 2.0)//训练集生成 ,规定数据结构为LabeledPoint == 构建方式:稀疏向量模式  ,2.0:类别编号LabeledPoint train_two = new LabeledPoint(2.0,sparse); //(2.0, 3.0, 0.0)//训练集生成 ,规定数据结构为LabeledPoint == 构建方式:稠密向量模式  ,3.0:类别编号LabeledPoint train_three = new LabeledPoint(3.0,Vectors.dense(1,1,2)); //(1.0, 1.0, 2.0)//List存放训练集【三个训练样本数据】List<LabeledPoint> trains = new ArrayList<>();trains.add(train_one);trains.add(train_two);trains.add(train_three);//获得弹性分布式数据集JavaRDD,数据类型为LabeledPointJavaRDD<LabeledPoint> trainingRDD = sc.parallelize(trains); /*** 利用Spark进行数据分析时,数据一般要转化为RDD* JavaRDD转Spark的RDD*/NaiveBayesModel nb_model = NaiveBayes.train(trainingRDD.rdd());//测试集生成double []  dTest = {2,1,0};Vector vTest =  Vectors.dense(dTest);//测试对象为单个vector,或者是RDD化后的vector//朴素贝叶斯用法System.err.println(nb_model.predict(vTest));// 分类结果 == 返回分类的标签值/*** 计算测试目标向量与训练样本数据集里面对应的各个分类标签匹配的概率结果*/System.err.println(nb_model.predictProbabilities(vTest)); //最后不要忘了释放资源sc.close();

}

}
运行之后就可以简单的理解一下hanlp的工作原理,我们就分词成功了

大体上计算机在接收到我们的问题后,它就会用hanlp分词器将我们的问题分成各个词,然后再用贝叶斯分类器(在训练后)就可以通过算法计算向量权重等来识别我们的问题到底是什么,然后再通过neo4j再知识图谱数据库中找到给我们的答案。

那么了解了hanlp后,我们再仔细了解一下朴素贝叶斯分类器。
贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类。
贝叶斯定理假设H[1],H[2]…,H[n]互斥且构成一个完全事件,已知它们的概率P(H[i]),i=1,2,…,n,现观察到某事件A与H[1],H[2]…,H[n]相伴随机出现,且已知条件概率P(A|H[i]),求P(H[i]|A)。
但看算法很抽象,我们就用博主给的范例来测试一下。
我们把男性和女性的特征做一个合并
总过6列

短发(1) 长发(2) 运动鞋(3) 高跟鞋(4) 喉结(5) 皮肤白(6)
上述我们可以将这些关键特征做成词汇表,可以在我们大实验中用。
(2)假设男性的特征有:短发、运动鞋、喉结这三个,则其分类标签的向量表示我们可以用
(1,0,1,0,1,0) > 1表示对应的特征向量值等于true【有】,0表示false【无】
(3)假设女性的特征有:长发、短发、运动鞋、高跟鞋、皮肤白这五个,则其分类标签的向量表示我们可以用
(1,1,1,1,0,1) == 1表示对应的特征向量值等于true【有】,0表示false【无】
(4)假设我们提供了一个人的测试数据,其具有短发(1),运动鞋(3)这两个特征,则用向量表示我们可以用
(1,0,1,0,0,0) 1表示对应的特征向量值等于true【有】,0表示false【无】
(5)实例化SparkContext对象
/**

  • 本地模式,*表示启用多个线程并行计算
    /
    SparkConf conf = new SparkConf().setAppName(“NaiveBayesTest”).setMaster("local[
    ]");
    JavaSparkContext sc = new JavaSparkContext(conf);
    (6)定义男性和女性两种分类的向量

/**

  • MLlib的本地向量主要分为两种,DenseVector和SparseVector
  • 前者是用来保存稠密向量,后者是用来保存稀疏向量
    */

/**

  • 两种方式分别创建向量 == 其实创建稀疏向量的方式有两种,本文只讲一种
  • (1.0, 0.0, 1.0, 0.0, 1.0, 0.0)
  • (1.0, 1.0, 1.0, 1.0, 0.0, 1.0)
    */

//稠密向量 == 连续的
Vector vMale = Vectors.dense(1,0,1,0,1,0);
//稀疏向量 == 间隔的、指定的,未指定位置的向量值默认 = 0.0
int len = 6;
int[] index = new int[]{0,1,2,3,5};
double[] values = new double[]{1,1,1,1,1};
//索引0、1、2、3、5位置上的向量值=1,索引4没给出,默认0
Vector vFemale = Vectors.sparse(len, index, values);
女性的向量我就不解释了
这里,你可以打印两种向量对象,看看输出值是什么,比如,这里我打印稀疏向量vFemale的值,效果如下

(7)生成训练集,类型 == LabelPoint

/**

  • labeled point 是一个局部向量,要么是密集型的要么是稀疏型的
  • 用一个label/response进行关联
  • 在MLlib里,labeled points 被用来监督学习算法
  • 我们使用一个double数来存储一个label,因此我们能够使用labeled points进行回归和分类
  • 在二进制分类里,一个label可以是 0(负数)或者 1(正数)
  • 在多级分类中,labels可以是class的索引,从0开始:0,1,2,…
    */

//训练集生成 ,规定数据结构为LabeledPoint == 构建方式:稠密向量模式 ,1.0:类别编号 == 男性
LabeledPoint train_one = new LabeledPoint(1.0,vMale); //(1.0, 0.0, 1.0, 0.0, 1.0, 0.0)
//训练集生成 ,规定数据结构为LabeledPoint == 构建方式:稀疏向量模式 ,2.0:类别编号 == 女性
LabeledPoint train_two = new LabeledPoint(2.0,vFemale); //(1.0, 1.0, 1.0, 1.0, 0.0, 1.0)
//我们也可以给同一个类别增加多个训练集
LabeledPoint train_three = new LabeledPoint(2.0,Vectors.dense(0,1,1,1,0,1));
训练样本集越多,贝叶斯分类器算法得到的分类结果越精确
(8)List集合存放训练集样本

//List存放训练集【三个训练样本数据】
List trains = new ArrayList<>();
trains.add(train_one);
trains.add(train_two);
trains.add(train_three);
(9)获得JavaRDD

/**

  • SPARK的核心是RDD(弹性分布式数据集)
  • Spark是Scala写的,JavaRDD就是Spark为Java写的一套API
  • JavaSparkContext sc = new JavaSparkContext(sparkConf); //对应JavaRDD
  • SparkContext sc = new SparkContext(sparkConf) ; //对应RDD
  • 数据类型为LabeledPoint
    */
    JavaRDD trainingRDD = sc.parallelize(trains);
    (10)JavaRDD转RDD【Scala】,并利用贝叶斯分类器对RDD数据集进行训练

/**

  • 利用Spark进行数据分析时,数据一般要转化为RDD
  • JavaRDD转Spark的RDD
    */
    NaiveBayesModel nb_model = NaiveBayes.train(trainingRDD.rdd());
    (11)模拟测试集数据 == 稠密向量【一个人拥有特征:短发,运动鞋】
    //测试集生成 == 以下的向量表示,这个人具有特征:短发(1),运动鞋(3)
    double [] dTest = {1,0,1,0,0,0};
    Vector vTest = Vectors.dense(dTest);//测试对象为单个vector,或者是RDD化后的vector
    (12)贝叶斯分类器分类测试

//朴素贝叶斯用法
int modelIndex =(int) nb_model.predict(vTest);
System.out.println(“标签分类编号:”+modelIndex);// 分类结果 == 返回分类的标签值
/**

  • 计算测试目标向量与训练样本数据集里面对应的各个分类标签匹配的概率结果
    */
    System.out.println(nb_model.predictProbabilities(vTest));
    if(modelIndex == 1){
    System.out.println(“答案:贝叶斯分类器推断这个人的性别是男性”);
    }else if(modelIndex == 2){
    System.out.println(“答案:贝叶斯分类器推断这个人的性别是男性”);
    }
    这一步才算是结果计算:分类器拿到测试数据样本,并和已经训练好的训练集样本进行概率匹配,而训练集样本又是有分类标签号标注的,因此,贝叶斯分类器最后计算返回的结果就是概率最大的那个分类标签号,也就是下面我会提到的问题模板的索引
    (13)最后一步,别忘了关闭sc资源
    //最后不要忘了释放资源
    sc.close();
    标签分类编号:1
    [0.5524861878453037,0.4475138121546963]
    答案:贝叶斯分类器推断这个人的性别是男性
    明显,根据提供的两个特征,短发和运动鞋,贝叶斯分类器计算的最终答案是男性,因为,具有该特征的男性的概率要大于具有该特征的女性的概率
    如果你对这个结果抱有怀疑的态度,你可以再加个特征,高跟鞋(4),试一试
    测试数据集向量数组: double [] dTest = {1,0,1,1,0,0};
    为什么有高跟鞋特征是女性的概率不是百分百或是百分之九十呢?
    首先,短发和运动鞋,这两个特征男性和女性的可能性都有

其次,高跟鞋虽然是女性的特征,但却不是唯一能决定性别走向的因素,因为短发的男生也是有可能穿高跟鞋的,因此,我们不难发现,贝叶斯概率公式真的很NB,他不是乱来的,你以为穿高跟鞋的绝对是女性,但是贝叶斯分类器告诉你,这种概率只有59%,而不是100%!!!
我们再来测试一组,比如一个人具有特征:短发(1),高跟鞋(4),喉结(5)
测试数据集向量: double [] dTest = {1,0,0,1,1,0};
按理说,有男性特征喉结,就能说明这个人八九不离十就是男性,我们看一下贝叶斯分类器的计算结果是否和我们的猜想吻合

测试结果正确。
那么上面我们简单的理解了我们需要用的贝叶斯分类器。那么我们成功分类了我们的问题,也就是机器已经知道了我们问的是什么问题,那么接下来机器需要做什么呢?自然是回答我们的问题,而又如何回答问题呢?
那就是通过neo4j数据库来查询到我们咨询的问题,从而做出回答。
既然是一种数据库,那么万变不离其宗就是增删改查
我们可以创建节点,修改节点,给节点赋予属性,而这个属性就是该节点的标签,那么我们在各个节点之间创建关系,就可达到两节点的关联。
在我们这个项目中,我们用周润发来举个例子

这样,比如我们问的是周润发演过什么电影,或者跟周润发有关系的一些东西,当计算机识别到我们的问题后,他就可以通过和neo4j数据库的连接来进行关系等的查询,进而回答我们。
那么至此我们整个项目的大体算法和技术软件支持就简单的介绍完毕了。
6, 问题解决
在这次大作业中,跑通这个项目可谓是困难重重,从头到尾大致总结一下。
1, 首先是各个工具环境的配置,需要比较细心,首先是需要找到下载路径,由于国内网络问题,想要配置某些环境需要到国外网站下载,网速非常慢,甚至根本无法下载。这就需要找到适用的网站,或者找到代理源,但是代理源有时也会遇到其他问题,这个我们下面再谈。
2, 而且在下载的时候我们需要找到项目中原本的版本,因为这个项目是18年的,里面很多需要的环境配置已经是比较老的版本,有些软件的版本博主并没有发出来,有的软件版本已经找不到或者无法下载,所以很难受。但是你又不得不下载适合的版本,因为不同的版本不兼容,冲突起来问题就很多。比如Idea和Maven如果版本冲突,你就根本无法从网上导入任何包,这就会导致你的所有程序都会报错,一万个错,这就会导致如果你没有发现是maven版本冲突的问题,你就会感觉有一万个问题需要解决,然后就崩溃了。
3, 解决完各种软件版本的冲突后,你还得解决hanlp词包的版本问题,并且在导入官方分词包后,不要忘了还得导入博主自己的分词包,这在里面起到一个很关键的分词作用。
4, 还有一点,如果你之前因为国外的包源下载速度太慢,于是在maven中设置了代理源的话,那么在用maven导包的时候还会出现504网络连接超时的问题,于是你还得在maven的setting文件中把代理源关闭,要不然你又会卡很久。
5, 最后不能忘了,在你启动整个项目之前,需要在idea中配置好jdk,tomcat,并且在终端中开启neo4j,这样项目才能连接上。
1, Neo4j的相关操作
Neo4j在处理节点(对象)关系这方面性能比较棒,而且查询语句简单,更容易构建我们的电影知识图谱,本系列实验中,参考博主采用neo4j来构建项目,所以我们自然需要安装Neo4j来储存对对象的数据处理。
首先,我们需要下载Neo4j的安装包,由于从官网下载速度太慢,我们选择了网上国内的资源,下载的版本位3.5.5。
下载后解压到任意文件夹,下面是本次作业中我的Neo4j的安装路径:E:\neo4j-community-3.5.5
之后我们需要将Neo4j的环境变量配置好:
编辑完路径后,测试一下是否安装成功:

 安装成功后我们就可以将准备好了CSV数据文件导入到Neo4j中:如图我们已经成功地将数据导入Neo4j的默认路径了,下面需要将数据导入Neo4j中:

找到neo4j的安装路径,并在D:\neo4j-community-3.4.0\目录下创建import目录
完整路径如下D:\neo4j-community-3.4.0\import
因为neo4j支持导入csv文件,其默认目录入口是 …\import

//导入节点 电影类型 == 注意类型转换
LOAD CSV WITH HEADERS FROM “file:///genre.csv” AS line
MERGE (p:Genre{gid:toInteger(line.gid),name:line.gname})

//导入节点 演员信息
LOAD CSV WITH HEADERS FROM ‘file:///person.csv’ AS line
MERGE (p:Person { pid:toInteger(line.pid),birth:line.birth,
death:line.death,name:line.name,
biography:line.biography,
birthplace:line.birthplace})

// 导入节点 电影信息
LOAD CSV WITH HEADERS FROM “file:///movie.csv” AS line
MERGE (p:Movie{mid:toInteger(line.mid),title:line.title,introduction:line.introduction,
rating:toFloat(line.rating),releasedate:line.releasedate})

// 导入关系 actedin 电影是谁参演的 1对多
LOAD CSV WITH HEADERS FROM “file:///person_to_movie.csv” AS line
match (from:Person{pid:toInteger(line.pid)}),(to:Movie{mid:toInteger(line.mid)})
merge (from)-[r:actedin{pid:toInteger(line.pid),mid:toInteger(line.mid)}]->(to)

//导入关系 电影是什么类型 == 1对多
LOAD CSV WITH HEADERS FROM “file:///movie_to_genre.csv” AS line
match (from:Movie{mid:toInteger(line.mid)}),(to:Genre{gid:toInteger(line.gid)})
merge (from)-[r:is{mid:toInteger(line.mid),gid:toInteger(line.gid)}]->(to)

导入完成后:测试一下:match (n)-[r:actedin]-(b) return n,r,b limit 10

2, Spark环境搭建
步骤如下:
====第一步:安装Java1.8 JDK,配置JAVA_HOME环境变量

下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk-netbeans-jsp-142931.html

JAVA_HOME = D:\java
path = …;%JAVA_HOME%\bin;

====第二步:安装Scala环境【Spark依赖于scala语言环境】

选择tools目录下的scala-2.10.5.msi【windows下可执行安装】

注意:安装的目录名一定不能带空格,可以再D盘下创建安装根目录D:\scala

安装完成后配置 scala环境变量

SCALA_HOME = D:\scala
path = …;%SCALA_HOME%\bin;

===第三步:配置hadoop环境

选择tools目录下的hadoop-3.0.1.tar.gz,解压,并重命名目录,最终路径形式为

D:\hadoop

解压完成后,配置hadoop系统环境变量

HADOOP_HOME=D:\hadoop
path=…;%HADOOP_HOME%\bin;

hadoop这里需要注意一个地方,需要将tools目录下的winutils.exe文件拷贝到hadoop的bin目录下

如果没有这个否则会出现 java.io.IOException: Could not locate executable null\bin\winutils.exe in the Hadoop binaries.

拷贝后,在D盘新建一个二级目录 D:\tmp\hive

然后cmd切换到D:\hadoop\bin\目录下,并执行
D:\hadoop\bin>winutils.exe chmod 777 D:/tmp/hive == 赋权限

===第四步:安装spark并配置环境变量
选择tools目录下的spark-2.3.0-bin-hadoop2.6.tgz,解压,并重命名目录,最终路径形式为

D:\spark

然后和上面雷同,配置spark环境变量

随后任意目录cmd执行 spark-shell,即可验证spark环境是否搭建成功。

3, Hanlp环境搭建
下载hanlp的各种东西
方法1.maven方法,下载一个0配置即可。【但是我不太会用】
方法2:先下载hanlp-1.2.8.jar这个jar包
http://hanlp.linrunsoft.com/services.html
再下载data.zip这个数据包,可以选择,选择下载标准数据or迷你数据or全部数据。大小不同。我下的是标准版的。40M
再下载hanlp.properties这个是一个以properties结尾的一个文件,我之前从来没见过,不过可以用txt打开。
第四步:把下载的这些东西导入到eclipse里面去。构建路径
1.把jar包导入到eclipse的lib目录下
http://jingyan.baidu.com/article/ca41422fc76c4a1eae99ed9f.html
2.自己在src里面创建一个包,在包里面创建一个类。包会在我设置的根目录D:/下面,类名称首字母必须大写?【貌似不大写的话,会被否决】
3.把data包解压,然后放在一个自己喜欢的路径【我的路径是D://py/】然后,在hanlp.properties这个文件里,把root修改为data存放的上一级目录。
4.把hanlp.properties拖动到src这个目录下。
这里我们用的是idea里面的maven,所以基本不用手动导入

它会自动导入你需要的包,非常实用,方便。
但这里需要注意的是,hanlp的词库需要你自己放到本机的正确位置,并且将需要的自定义词库放进去。

上图的红框内就是需要的自定义词库。

4, 项目运行
做完这些其实你为项目进行的环境配置已经足够了,那么我们可以在博主给的github上下载我们的智能问答系统运行一下。

有了上图SPRING的标志就代表着我们的项目正常运行了。
在运行之前还需要将之前配置的Neo4j启动

还需要在idea中将tomcat配置好,因为我们的前端是在web服务器上来进行的。

接下来就可以区localhost:8080看一下我们的项目是否运行成功:

可以看到我们的项目已经跑起来了。

5, 算法原理理解:
那么正常的跑起来项目我们已经做到了,但是更重要的是我们要理解这个问答系统的算法原理。
其中最重要的一点就是我们需要理解机器是如何识别我们的问题,并且分类理解我们的问题如何解答呢?
事实上为了实现上面这点,在这个项目中运用的是Hanlp+朴素贝叶斯分类器来进行机器学习,用部分数据训练,这样机器就可以识别我们的问题到底属于哪一类。
Hanlp分词就用hanlp自带的方法,这里我们编译一个小的测试范例来熟悉一下hanlp的工作原理。
下面附上理解hanlp的测试代码,并附上注释:
import java.util.ArrayList;
import java.util.List;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.mllib.classification.NaiveBayes;
import org.apache.spark.mllib.classification.NaiveBayesModel;
import org.apache.spark.mllib.linalg.Vector;
import org.apache.spark.mllib.linalg.Vectors;
import org.apache.spark.mllib.regression.LabeledPoint;
import org.junit.Test;

import com.appleyk.process.ModelProcess;
import com.hankcs.hanlp.HanLP;
import com.hankcs.hanlp.dictionary.CustomDictionary;
import com.hankcs.hanlp.seg.Segment;
import com.hankcs.hanlp.seg.common.Term;

public class HanLPTest {

@Test
public void TestA(){
String lineStr = “明天虽然会下雨,但是我还是会看周杰伦的演唱会。”;
try{
Segment segment = HanLP.newSegment();
segment.enableCustomDictionary(true);
/**
* 自定义分词+词性
*/
CustomDictionary.add(“虽然会”,“ng 0”);
List seg = segment.seg(lineStr);
for (Term term : seg) {
System.out.println(term.toString());
}
}catch(Exception ex){
System.out.println(ex.getClass()+","+ex.getMessage());
}
}
@Test
public void TestB(){
HanLP.Config.Normalization = true;
CustomDictionary.insert(“爱听4G”, “nz 1000”);
System.out.println(HanLP.segment(“爱听4g”));
System.out.println(HanLP.segment(“爱听4G”));
System.out.println(HanLP.segment(“爱听4G”));
System.out.println(HanLP.segment(“爱听4G”));
System.out.println(HanLP.segment(“愛聽4G”));
}

@Test
public void TestC() throws Exception{
ModelProcess query = new ModelProcess(“D:/HanLP/data”);
String[] questionArr = new String[] {“卧虎藏龙的分数是多少”};
for(String que: questionArr){
ArrayList question = query.analyQuery(que);
System.err.println(question);
}
}

@Test
public void TestRDD(){

  SparkConf conf = new SparkConf().setAppName("NaiveBayesTest").setMaster("local[*]");JavaSparkContext sc = new JavaSparkContext(conf);/*** MLlib的本地向量主要分为两种,DenseVector和SparseVector* 前者是用来保存稠密向量,后者是用来保存稀疏向量     *//*** 两种方式分别创建向量  == 其实创建稀疏向量的方式有两种,本文只讲一种* (1.0, 0.0, 2.0)* (2.0, 3.0, 0.0)*///稠密向量 == 连续的Vector dense = Vectors.dense(1.0,0.0,2.0);System.out.println(dense);//稀疏向量 == 间隔的、指定的,未指定位置的向量值默认 = 0.0int len = 3;int[] index = new int[]{0,1};double[] values = new double[]{2.0,3.0};Vector sparse = Vectors.sparse(len, index, values);/*** labeled point 是一个局部向量,要么是密集型的要么是稀疏型的* 用一个label/response进行关联* 在MLlib里,labeled points 被用来监督学习算法* 我们使用一个double数来存储一个label,因此我们能够使用labeled points进行回归和分类* 在二进制分类里,一个label可以是 0(负数)或者 1(正数)* 在多级分类中,labels可以是class的索引,从0开始:0,1,2,......*///训练集生成 ,规定数据结构为LabeledPoint == 构建方式:稠密向量模式  ,1.0:类别编号LabeledPoint train_one = new LabeledPoint(1.0,dense);  //(1.0, 0.0, 2.0)//训练集生成 ,规定数据结构为LabeledPoint == 构建方式:稀疏向量模式  ,2.0:类别编号LabeledPoint train_two = new LabeledPoint(2.0,sparse); //(2.0, 3.0, 0.0)//训练集生成 ,规定数据结构为LabeledPoint == 构建方式:稠密向量模式  ,3.0:类别编号LabeledPoint train_three = new LabeledPoint(3.0,Vectors.dense(1,1,2)); //(1.0, 1.0, 2.0)//List存放训练集【三个训练样本数据】List<LabeledPoint> trains = new ArrayList<>();trains.add(train_one);trains.add(train_two);trains.add(train_three);//获得弹性分布式数据集JavaRDD,数据类型为LabeledPointJavaRDD<LabeledPoint> trainingRDD = sc.parallelize(trains); /*** 利用Spark进行数据分析时,数据一般要转化为RDD* JavaRDD转Spark的RDD*/NaiveBayesModel nb_model = NaiveBayes.train(trainingRDD.rdd());//测试集生成double []  dTest = {2,1,0};Vector vTest =  Vectors.dense(dTest);//测试对象为单个vector,或者是RDD化后的vector//朴素贝叶斯用法System.err.println(nb_model.predict(vTest));// 分类结果 == 返回分类的标签值/*** 计算测试目标向量与训练样本数据集里面对应的各个分类标签匹配的概率结果*/System.err.println(nb_model.predictProbabilities(vTest)); //最后不要忘了释放资源sc.close();

}

}
运行之后就可以简单的理解一下hanlp的工作原理,我们就分词成功了

大体上计算机在接收到我们的问题后,它就会用hanlp分词器将我们的问题分成各个词,然后再用贝叶斯分类器(在训练后)就可以通过算法计算向量权重等来识别我们的问题到底是什么,然后再通过neo4j再知识图谱数据库中找到给我们的答案。

那么了解了hanlp后,我们再仔细了解一下朴素贝叶斯分类器。
贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类。
贝叶斯定理假设H[1],H[2]…,H[n]互斥且构成一个完全事件,已知它们的概率P(H[i]),i=1,2,…,n,现观察到某事件A与H[1],H[2]…,H[n]相伴随机出现,且已知条件概率P(A|H[i]),求P(H[i]|A)。
但看算法很抽象,我们就用博主给的范例来测试一下。
我们把男性和女性的特征做一个合并
总过6列

短发(1) 长发(2) 运动鞋(3) 高跟鞋(4) 喉结(5) 皮肤白(6)
上述我们可以将这些关键特征做成词汇表,可以在我们大实验中用。
(2)假设男性的特征有:短发、运动鞋、喉结这三个,则其分类标签的向量表示我们可以用
(1,0,1,0,1,0) > 1表示对应的特征向量值等于true【有】,0表示false【无】
(3)假设女性的特征有:长发、短发、运动鞋、高跟鞋、皮肤白这五个,则其分类标签的向量表示我们可以用
(1,1,1,1,0,1) == 1表示对应的特征向量值等于true【有】,0表示false【无】
(4)假设我们提供了一个人的测试数据,其具有短发(1),运动鞋(3)这两个特征,则用向量表示我们可以用
(1,0,1,0,0,0) 1表示对应的特征向量值等于true【有】,0表示false【无】
(5)实例化SparkContext对象
/**

  • 本地模式,*表示启用多个线程并行计算
    /
    SparkConf conf = new SparkConf().setAppName(“NaiveBayesTest”).setMaster("local[
    ]");
    JavaSparkContext sc = new JavaSparkContext(conf);
    (6)定义男性和女性两种分类的向量

/**

  • MLlib的本地向量主要分为两种,DenseVector和SparseVector
  • 前者是用来保存稠密向量,后者是用来保存稀疏向量
    */

/**

  • 两种方式分别创建向量 == 其实创建稀疏向量的方式有两种,本文只讲一种
  • (1.0, 0.0, 1.0, 0.0, 1.0, 0.0)
  • (1.0, 1.0, 1.0, 1.0, 0.0, 1.0)
    */

//稠密向量 == 连续的
Vector vMale = Vectors.dense(1,0,1,0,1,0);
//稀疏向量 == 间隔的、指定的,未指定位置的向量值默认 = 0.0
int len = 6;
int[] index = new int[]{0,1,2,3,5};
double[] values = new double[]{1,1,1,1,1};
//索引0、1、2、3、5位置上的向量值=1,索引4没给出,默认0
Vector vFemale = Vectors.sparse(len, index, values);
女性的向量我就不解释了
这里,你可以打印两种向量对象,看看输出值是什么,比如,这里我打印稀疏向量vFemale的值,效果如下

(7)生成训练集,类型 == LabelPoint

/**

  • labeled point 是一个局部向量,要么是密集型的要么是稀疏型的
  • 用一个label/response进行关联
  • 在MLlib里,labeled points 被用来监督学习算法
  • 我们使用一个double数来存储一个label,因此我们能够使用labeled points进行回归和分类
  • 在二进制分类里,一个label可以是 0(负数)或者 1(正数)
  • 在多级分类中,labels可以是class的索引,从0开始:0,1,2,…
    */

//训练集生成 ,规定数据结构为LabeledPoint == 构建方式:稠密向量模式 ,1.0:类别编号 == 男性
LabeledPoint train_one = new LabeledPoint(1.0,vMale); //(1.0, 0.0, 1.0, 0.0, 1.0, 0.0)
//训练集生成 ,规定数据结构为LabeledPoint == 构建方式:稀疏向量模式 ,2.0:类别编号 == 女性
LabeledPoint train_two = new LabeledPoint(2.0,vFemale); //(1.0, 1.0, 1.0, 1.0, 0.0, 1.0)
//我们也可以给同一个类别增加多个训练集
LabeledPoint train_three = new LabeledPoint(2.0,Vectors.dense(0,1,1,1,0,1));
训练样本集越多,贝叶斯分类器算法得到的分类结果越精确
(8)List集合存放训练集样本

//List存放训练集【三个训练样本数据】
List trains = new ArrayList<>();
trains.add(train_one);
trains.add(train_two);
trains.add(train_three);
(9)获得JavaRDD

/**

  • SPARK的核心是RDD(弹性分布式数据集)
  • Spark是Scala写的,JavaRDD就是Spark为Java写的一套API
  • JavaSparkContext sc = new JavaSparkContext(sparkConf); //对应JavaRDD
  • SparkContext sc = new SparkContext(sparkConf) ; //对应RDD
  • 数据类型为LabeledPoint
    */
    JavaRDD trainingRDD = sc.parallelize(trains);
    (10)JavaRDD转RDD【Scala】,并利用贝叶斯分类器对RDD数据集进行训练

/**

  • 利用Spark进行数据分析时,数据一般要转化为RDD
  • JavaRDD转Spark的RDD
    */
    NaiveBayesModel nb_model = NaiveBayes.train(trainingRDD.rdd());
    (11)模拟测试集数据 == 稠密向量【一个人拥有特征:短发,运动鞋】
    //测试集生成 == 以下的向量表示,这个人具有特征:短发(1),运动鞋(3)
    double [] dTest = {1,0,1,0,0,0};
    Vector vTest = Vectors.dense(dTest);//测试对象为单个vector,或者是RDD化后的vector
    (12)贝叶斯分类器分类测试

//朴素贝叶斯用法
int modelIndex =(int) nb_model.predict(vTest);
System.out.println(“标签分类编号:”+modelIndex);// 分类结果 == 返回分类的标签值
/**

  • 计算测试目标向量与训练样本数据集里面对应的各个分类标签匹配的概率结果
    */
    System.out.println(nb_model.predictProbabilities(vTest));
    if(modelIndex == 1){
    System.out.println(“答案:贝叶斯分类器推断这个人的性别是男性”);
    }else if(modelIndex == 2){
    System.out.println(“答案:贝叶斯分类器推断这个人的性别是男性”);
    }
    这一步才算是结果计算:分类器拿到测试数据样本,并和已经训练好的训练集样本进行概率匹配,而训练集样本又是有分类标签号标注的,因此,贝叶斯分类器最后计算返回的结果就是概率最大的那个分类标签号,也就是下面我会提到的问题模板的索引
    (13)最后一步,别忘了关闭sc资源
    //最后不要忘了释放资源
    sc.close();
    标签分类编号:1
    [0.5524861878453037,0.4475138121546963]
    答案:贝叶斯分类器推断这个人的性别是男性
    明显,根据提供的两个特征,短发和运动鞋,贝叶斯分类器计算的最终答案是男性,因为,具有该特征的男性的概率要大于具有该特征的女性的概率
    如果你对这个结果抱有怀疑的态度,你可以再加个特征,高跟鞋(4),试一试
    测试数据集向量数组: double [] dTest = {1,0,1,1,0,0};
    为什么有高跟鞋特征是女性的概率不是百分百或是百分之九十呢?
    首先,短发和运动鞋,这两个特征男性和女性的可能性都有

其次,高跟鞋虽然是女性的特征,但却不是唯一能决定性别走向的因素,因为短发的男生也是有可能穿高跟鞋的,因此,我们不难发现,贝叶斯概率公式真的很NB,他不是乱来的,你以为穿高跟鞋的绝对是女性,但是贝叶斯分类器告诉你,这种概率只有59%,而不是100%!!!
我们再来测试一组,比如一个人具有特征:短发(1),高跟鞋(4),喉结(5)
测试数据集向量: double [] dTest = {1,0,0,1,1,0};
按理说,有男性特征喉结,就能说明这个人八九不离十就是男性,我们看一下贝叶斯分类器的计算结果是否和我们的猜想吻合

测试结果正确。
那么上面我们简单的理解了我们需要用的贝叶斯分类器。那么我们成功分类了我们的问题,也就是机器已经知道了我们问的是什么问题,那么接下来机器需要做什么呢?自然是回答我们的问题,而又如何回答问题呢?
那就是通过neo4j数据库来查询到我们咨询的问题,从而做出回答。
既然是一种数据库,那么万变不离其宗就是增删改查
我们可以创建节点,修改节点,给节点赋予属性,而这个属性就是该节点的标签,那么我们在各个节点之间创建关系,就可达到两节点的关联。
在我们这个项目中,我们用周润发来举个例子

这样,比如我们问的是周润发演过什么电影,或者跟周润发有关系的一些东西,当计算机识别到我们的问题后,他就可以通过和neo4j数据库的连接来进行关系等的查询,进而回答我们。
那么至此我们整个项目的大体算法和技术软件支持就简单的介绍完毕了。
6, 问题解决
在这次大作业中,跑通这个项目可谓是困难重重,从头到尾大致总结一下。
1, 首先是各个工具环境的配置,需要比较细心,首先是需要找到下载路径,由于国内网络问题,想要配置某些环境需要到国外网站下载,网速非常慢,甚至根本无法下载。这就需要找到适用的网站,或者找到代理源,但是代理源有时也会遇到其他问题,这个我们下面再谈。
2, 而且在下载的时候我们需要找到项目中原本的版本,因为这个项目是18年的,里面很多需要的环境配置已经是比较老的版本,有些软件的版本博主并没有发出来,有的软件版本已经找不到或者无法下载,所以很难受。但是你又不得不下载适合的版本,因为不同的版本不兼容,冲突起来问题就很多。比如Idea和Maven如果版本冲突,你就根本无法从网上导入任何包,这就会导致你的所有程序都会报错,一万个错,这就会导致如果你没有发现是maven版本冲突的问题,你就会感觉有一万个问题需要解决,然后就崩溃了。
3, 解决完各种软件版本的冲突后,你还得解决hanlp词包的版本问题,并且在导入官方分词包后,不要忘了还得导入博主自己的分词包,这在里面起到一个很关键的分词作用。
4, 还有一点,如果你之前因为国外的包源下载速度太慢,于是在maven中设置了代理源的话,那么在用maven导包的时候还会出现504网络连接超时的问题,于是你还得在maven的setting文件中把代理源关闭,要不然你又会卡很久。
5, 最后不能忘了,在你启动整个项目之前,需要在idea中配置好jdk,tomcat,并且在终端中开启neo4j,这样项目才能连接上。

电影知识图谱的智能问答系统在本机跑通实验及遇到问题的解决相关推荐

  1. mysql实现知识图谱_基于电影知识图谱的智能问答系统学习记录

    学习了"谓之小一"大佬的基于电影知识图谱的智能问答系统,做个记录.地址如下:https://github.com/weizhixiaoyi/DouBan-KGQA 一.知识图谱构建 ...

  2. 基于neo4j知识图谱的智能问答系统

    项目介绍 本项目采用neo4j作为数据库,存储了知识题库.用户可以根据提示深入去了解问题.属于一款简易版的智能问答系统. 服务端使用技术:python+django框架 前台使用:html+css+j ...

  3. 基于学者网信息和知识图谱的智能问答系统

    知识图谱 之 python 操作neo4j (导入CSV操作) 知识图谱之python操作Neo4j导入CSV构建复杂的知识图谱 基于python的Django框架和Neo4j的知识图谱可视化 大家可 ...

  4. NLP精选10个实现项目推荐-涉及预训练Bert、知识图谱、智能问答、机器翻译、对话等...

    自然语言处理技术近几年发展非常快,像BERT.GPT-3.图神经网络.知识图谱等技术被大量应用于项目实践中. 今年大厂的NLP面试中对项目方面的考察深度也随之提升了很多,经常会被面试官揪着细节一步一步 ...

  5. 知识图谱在智能搜索领域的应用

    在科技的推动下,每一个领域都在不断的进步.而搜索引擎领域,也在用户需求和人工智能的推动下,逐渐革新. 传统的搜索引擎,在一定程度上解决了用户从互联网中获取信息的难题,但由于它们是基于关键词或字符串的, ...

  6. 自然语言处理(NLP):20 基于知识图谱的智能问答系统

    基于知识图谱的问答系统答即根据一个问题,抽出一条三元组,生成类 sql 语句,知识图谱查询返回答案.本文是基于知识图谱的问答系统,通过 BERT+CRF 做命名实体识别和句子相似度比较,最后实现线上的 ...

  7. 10大NLP精选项目-涉及预训练Bert、知识图谱、智能问答、机器翻译、对话等

    自然语言处理技术近几年发展非常快,像BERT.GPT-3.图神经网络.知识图谱等技术被大量应用于项目实践中. 今年大厂的NLP面试中对项目方面的考察深度也随之提升了很多,经常会被面试官揪着细节一步一步 ...

  8. 强烈推荐十大NLP主流经典项目:预训练BERT、知识图谱、智能问答、机器翻译、文本自动生成等...

    自然语言处理技术近几年发展非常快,像BERT.GPT-3.图神经网络.知识图谱等技术被大量应用于项目实践中. 今年大厂的NLP面试中对项目方面的考察深度也随之提升了很多,经常会被面试官揪着细节一步一步 ...

  9. 使用Neo4j+InteractiveGraph实现豆瓣电影知识图谱可视化

    0.介绍 本文基于豆瓣电影数据构建了一个电影知识图谱.其中包括电影.演员.导演三种节点及相关关系.并使用InteractiveGraph对图谱完成可视化工作. 数据丰富,图谱包含2.7万个节点,5万条 ...

最新文章

  1. html语言制作留言条,利用DIV+CSS制作右下角弹出留言板
  2. c语言空中升级协议,esp32 http空中升级
  3. 备份集中的数据库备份与现有的 '***' 数据库不同 RESTORE DATABASEnbs
  4. 小熊派4G cat1模块体验测试报告,AT命令发送短信拨号无脑教程,cat1移动网速测试...
  5. 微软发布Azure Service Fabric Mesh公开预览版
  6. 十万字cpp成神总结-看完月薪25k
  7. 圣三一学院计算机专业,360教育集团:爱尔兰都柏林大学圣三一学院计算机专业...
  8. “Lephone.Data.DbEntry”的类型初始值设定项引发异常。(DbEntry.net3.9)
  9. 从著名的list_head看linux内核中OO 从Unix分层内核栈以及中断处理看Linux内核的另类
  10. 第二十三章:触发器和行为(九)
  11. 在执行某个行为时,其大脑是否产生了对应的稳定脑神经模式映射?如果存在稳定映射,是否能运用机器学习方法发现未知行为神经回路?
  12. 系统分析与设计期末复习题目
  13. 计算机excel中百分比怎么算,excel如何自动算百分比
  14. layui icon服务器上显示不出来,关于layui的动态图标不显示的解决方法
  15. 如何为水晶报表rpt文件添加数据连接
  16. Blurring the Lines between Blockchains and Database Systems: the Case of Hyperledger Fabric
  17. java导出excel带上进度条_导出数据至Excel前台js进度条不能隐藏
  18. CF乱码问题解决方案
  19. Hadoop的数据压缩
  20. 怎样抠图去背景?教你快速抠图的方法

热门文章

  1. 传说对决亚服服务器未响应,传说对决亚服
  2. Jedis常用API整理-详细
  3. 计算机毕业设计Java养老院管理系统(源码+系统+mysql数据库+Lw文档)
  4. 微财富兜底映射互联网金融弊端
  5. 电脑回收站图标更换pop猫——变异版--亮亮
  6. MySQL数据库再次复习【20220421】
  7. PHPExcel类导入excel表带图片
  8. 用MySQL实现类似微信朋友圈的屏蔽功能
  9. es5的data语法_ES5与ES6数组方法总结
  10. sqlserver日期格式转换